Contact data sources
With a contact data source, the person handling a conversation can query one of your company’s systems without leaving it. In the Contact tab, they select Look up and SquadOS asks your endpoint what it has on that person: plan, orders, invoices, funnel stage, event history. The answer shows up as a card made of fields, lists, and a timeline.
A lookup only happens when someone clicks. Nothing is sent to your source when a conversation arrives or when the tab opens.
Permissions
Section titled “Permissions”| Action | Capability |
|---|---|
| Create, edit, test, generate a new secret for, and delete sources | contact_data_sources.write |
| See the cards in a conversation and select Look up | contacts.view |
contact_data_sources.write starts with the Administrator role and is marked sensitive, because it decides where contact data is sent. contacts.view is already part of the Attendant package.
Add a source
Section titled “Add a source”- Open Settings and, under Developers, choose Data sources.
- Select New source.
- Fill in Name, up to 60 characters. This is the name shown in the conversation, for example “CRM” or “Orders”.
- Fill in URL. It must start with
https://. - Select Save.
After saving, SquadOS shows the source’s secret. Copy it with Copy secret and store it in your endpoint: it won’t be shown again. Your endpoint uses it to confirm the lookup came from SquadOS (see Verify the signature).
Each organization can have up to 5 sources. Once the limit is reached, the New source button no longer appears.
Test, edit, and delete
Section titled “Test, edit, and delete”Each source in the list has four actions:
- Test sends a real, signed lookup for a sample contact (Maria Exemplo,
+5511999990000,[email protected]) and shows the result: Valid response., Valid response (sample contact not found)., a response slower than 8 seconds, a rejected signature, or the exact path of the field that is out of format. Testing works even when the source is disabled. - Edit changes the name and URL and turns the source on or off with Enabled. A disabled source stays in the list, marked Disabled, and disappears from conversations.
- Generate new secret replaces the current secret immediately. Lookups fail until you paste the new secret into your endpoint.
- Delete removes the source, and its card no longer appears in conversations.
Look up a contact in a conversation
Section titled “Look up a contact in a conversation”On the conversation screen, open the Contact tab. Each enabled source appears as a collapsed band with the source name and a status summary: Not looked up, Looking up…, Found (or the title your source returned), Not found, or Error. If the organization has no enabled source, the area doesn’t appear.
Open the band and select Look up. After a result, the same button becomes Refresh; after an error, it becomes Try again.
The result stays only on the screen of whoever looked it up. It isn’t saved to the conversation, isn’t shown to other team members, and isn’t added to the AI agent’s context. When you reopen the conversation, look it up again.
Timeline and Load more
Section titled “Timeline and Load more”When your source returns a timeline, events are grouped by day, with an icon per category (purchase, billing, sales, conversation, messaging, lifecycle), a title, up to two lines of subtitle, and the time. An event with a link opens the matching page in a new tab.
If your source says there are more events, Load more fetches the next page and appends it to the list. Refresh fetches the card again and drops the extra pages already loaded.
Error messages
Section titled “Error messages”| Message | What to check |
|---|---|
| ”[source name] took too long to respond.” | The endpoint must answer within 8 seconds. |
| ”Couldn’t reach [source name].” | URL, DNS, certificate, a non-2xx HTTP status, or a redirect. SquadOS doesn’t follow redirects or call internal network addresses. |
| ”[source name] rejected the lookup. Check the secret in Settings › Data sources.” | The endpoint answered 401 or 403. Make sure it uses the current secret. |
| ”[source name] replied in a format SquadOS doesn’t understand.” | Use Test in the settings: it shows the path of the field that doesn’t follow the format. |
Request format
Section titled “Request format”SquadOS sends a POST to the registered URL with Content-Type: application/json and these headers:
| Header | Content |
|---|---|
X-Squados-Event | contact.lookup for a lookup; contact.timeline for Load more |
X-Squados-Delivery | Unique ID of the call |
X-Squados-Timestamp | Time of the call, in Unix seconds |
X-Squados-Signature | HMAC in the sha256=<hex> format |
contact.lookup body:
{ "version": 1, "event": "contact.lookup", "organization_id": "…", "locale": "en", "contact": { "id": "…", "display_name": "Maria Exemplo", "phone": "+5511999990000", "identities": [{ "type": "whatsapp", "value": "+5511999990000" }] }}locale is the language of the viewer’s screen; use it to return labels in the right language. phone, email, and display_name may be null. identities lists every known identity of the contact, such as WhatsApp, email, and Instagram handle.
contact.timeline uses the same body, with "event": "contact.timeline" and the cursor field your source returned.
Verify the signature
Section titled “Verify the signature”The signature follows the same scheme as event webhooks: HMAC-SHA256(secret, "<timestamp>.<raw body>"). If you already verify SquadOS webhooks, the same code works here. Check the signature against the raw body, before calling JSON.parse.
Response format
Section titled “Response format”Answer 200 with JSON. Contact not found:
{ "version": 1, "found": false }Contact found:
{ "version": 1, "found": true, "title": "Maria Exemplo", "subtitle": "Customer since 2024", "link": { "label": "Open in CRM", "url": "https://crm.example.com/contacts/123" }, "sections": [ { "kind": "fields", "title": "Subscription", "items": [ { "label": "Plan", "type": "badge", "value": "Pro", "tone": "success" }, { "label": "Monthly fee", "type": "currency", "value": 199.9, "currency": "USD" }, { "label": "Renewal", "type": "date", "value": "2026-10-15" } ] }, { "kind": "list", "title": "Open orders", "items": [ { "title": "Order #4821", "subtitle": "2 items", "meta": "Being packed", "url": "https://shop.example.com/orders/4821" } ] }, { "kind": "timeline", "title": "History", "items": [ { "at": "2026-09-20T14:32:00Z", "category": "purchase", "tone": "success", "title": "Purchase approved", "subtitle": "Pro plan, yearly" } ], "next_cursor": "page-2" } ]}Section kinds:
fields: label and value pairs.typeacceptstext,number,date,currency(withcurrencyas a three-letter ISO code, such asUSD),link(anhttpsURL), andbadge(withtone:neutral,success,warning, ordanger). Withouttype, the field is treated astext.list: items with a requiredtitleand optionalsubtitle,meta, andurl.timeline: events with a requiredat(ISO 8601 date) andtitle, and optionalcategory(purchase,billing,sales,conversation,messaging,lifecycle, orother),tone,subtitle, andurl. Only the card’s firsttimelinesection is shown.
The card’s title, subtitle, and link are optional. All text is rendered as plain text; HTML is not interpreted.
Paginate the timeline
Section titled “Paginate the timeline”To offer Load more, include next_cursor in the timeline section. It’s an opaque string of up to 500 characters that only your source understands. When someone asks for more, SquadOS sends contact.timeline with that cursor, and your source answers with the next page:
{ "version": 1, "items": [ { "at": "2026-08-02T09:10:00Z", "category": "billing", "title": "Invoice paid" } ], "next_cursor": null}A null or missing next_cursor ends pagination and the button disappears.
Limits and tolerance
Section titled “Limits and tolerance”- Response time: 8 seconds.
- Response size: 64 KB.
- Up to 20 sections per card and 50 items per section or page.
- Text longer than 500 characters is cut.
- Links are only shown when they use
https.
An invalid structure, such as a version other than 1, a missing found, or items that isn’t a list, rejects the whole response and shows the format error. A single invalid item, such as an unreadable date or a currency field without a code, is dropped on its own and the rest of the card still shows. Sections with an unknown kind are ignored.