Conversations & contacts API
The team inbox is available through the API, so your systems can route, answer and label conversations alongside your agents. These endpoints need two things:
- the inbox enabled for the project (otherwise
403 FEATURE_NOT_AVAILABLE) - an API key with the matching scope
| Scope | Allows |
|---|---|
conversations:read | List conversations, read timelines, list tags |
conversations:write | Reply, start conversations, change status/assignee/team/priority, tags, notes |
contacts:read | List and read contacts and tags |
contacts:write | Create, update, tag and erase contacts; manage tags |
New keys get these scopes by default. Keys created before the inbox existed must be re-created with them.
Conversations
A conversation is the thread between one of your instances and one contact. Its status is one of:
openpending(waiting for the customer)snoozedresolved
A new message from the contact reopens it.
curl https://api.whatstig.com/v1/conversations?status=open&assignee=none \
-H "Authorization: Bearer $WHATSTIG_API_KEY"
Filters for GET /v1/conversations:
| Parameter | Values |
|---|---|
status | open (also includes pending), pending, snoozed, resolved or all |
assignee | none or a user ID |
teamId, tagId, instanceId | Restrict to one team, tag or instance |
q | Contact name, number or message text |
sort | recent, oldest or waiting (customers waiting longest first) |
Results are paginated with cursor and limit; see Pagination.
GET /v1/conversations/{id}/timeline returns messages, internal notes and activity, oldest first. Page backwards with
the before cursor.
Replying
curl -X POST https://api.whatstig.com/v1/conversations/cnv_.../messages \
-H "Authorization: Bearer $WHATSTIG_API_KEY" \
-H "Idempotency-Key: 7b0c..." \
-H "Content-Type: application/json" \
-d '{"type":"text","content":{"text":"Your order shipped today."},"lastSeenMessageId":"msg_..."}'
- Response. Replies are queued like
POST /v1/messages: the response is202with statusQUEUED, and you track delivery throughmessage.*webhooks. AnIdempotency-Keyis required. - Recipient. The reply goes to the contact's phone number, or to its WhatsApp privacy ID (LID) when WhatsApp hides the number.
- Collisions. Pass
lastSeenMessageIdto fail with409 REPLY_COLLISIONif an agent or the phone replied after that message. Send again with"force": trueto reply anyway.
POST /v1/conversations starts a conversation (or continues the existing one) by writing to a phone number:
{ "instanceId", "recipient", "type", "content" }.
Changing a conversation
PATCH /v1/conversations/cnv_...
{ "status": "resolved", "version": 12 }
You can change these fields:
status(withsnoozedUntilwhen snoozing)assigneeUserId(a member with an inbox seat, ornull)teamIdpriority(low,normal,high,urgent)
Pass the version you read to get 409 CONFLICT if someone changed the conversation in the meantime.
PUT /v1/conversations/{id}/tagswith{ "tagIds": [...] }replaces the conversation's tags. Tags are listed atGET /v1/tagsand managed in the dashboard or withPOST /v1/tags.POST /v1/conversations/{id}/noteswith{ "body" }adds an internal note. Notes are never sent to WhatsApp. Mention a teammate with<@userId>.
Contacts
Contacts do not need the inbox: they are a core resource with tags, consent and imports. See Contacts, segments & campaigns API. Members restricted to their teams' conversations only see the contacts of those conversations.
Webhooks
| Event | When |
|---|---|
conversation.created | A new conversation starts |
conversation.assigned | The assignee changes |
conversation.status_changed | The status changes (includes previousStatus) |
Their data contains:
conversationId,contactId,phonestatus,previousStatusassigneeUserId,teamIdoccurredAt