Skip to content

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
ScopeAllows
conversations:readList conversations, read timelines, list tags
conversations:writeReply, start conversations, change status/assignee/team/priority, tags, notes
contacts:readList and read contacts and tags
contacts:writeCreate, 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:

  • open
  • pending (waiting for the customer)
  • snoozed
  • resolved

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:

ParameterValues
statusopen (also includes pending), pending, snoozed, resolved or all
assigneenone or a user ID
teamId, tagId, instanceIdRestrict to one team, tag or instance
qContact name, number or message text
sortrecent, 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 is 202 with status QUEUED, and you track delivery through message.* webhooks. An Idempotency-Key is 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 lastSeenMessageId to fail with 409 REPLY_COLLISION if an agent or the phone replied after that message. Send again with "force": true to 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 (with snoozedUntil when snoozing)
  • assigneeUserId (a member with an inbox seat, or null)
  • teamId
  • priority (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}/tags with { "tagIds": [...] } replaces the conversation's tags. Tags are listed at GET /v1/tags and managed in the dashboard or with POST /v1/tags.
  • POST /v1/conversations/{id}/notes with { "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

EventWhen
conversation.createdA new conversation starts
conversation.assignedThe assignee changes
conversation.status_changedThe status changes (includes previousStatus)

Their data contains:

  • conversationId, contactId, phone
  • status, previousStatus
  • assigneeUserId, teamId
  • occurredAt