Developers

Connect IgniChat to your own systems: read your data with an API key and receive events by webhook.

The technical documentation is in English.

Getting started

1. In IgniChat open Settings → Developer and create an API key. Choose only the scopes the integration needs. The key is shown once — store it somewhere safe.

2. Send it as a bearer token. Every request is scoped to the one workspace the key belongs to.

Use an ign_test_ key while you build: it works the same way but never touches live data.

List contacts
curl https://YOUR_API_HOST/v1/workspaces/WORKSPACE_ID/contacts?limit=20 \
  -H "Authorization: Bearer ign_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Authentication and scopes

A key is ign_live_… (real data) or ign_test_… (sandbox). It carries the scopes chosen when it was created; each operation in the reference states the scope it needs, and a key without it gets 403.

Version 1 is read-only. A write with an API key returns 403 with the code api_key.write_not_supported.

Revoke a key at any time in Settings → Developer; it stops working immediately.

Rate limits

Each key may make 300 requests per minute. Every response includes X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset (seconds until the window resets).

When the limit is exceeded the API answers 429 with a Retry-After header. Wait that many seconds and retry; do not retry in a tight loop.

Pagination

List operations are cursor based. Pass limit (1–100); the response has items and nextCursor. Send nextCursor back as cursor to get the next page; a null nextCursor means the end. There is no offset, so a page never repeats or skips a row while data changes.

Walk every page
curl "https://YOUR_API_HOST/v1/workspaces/WORKSPACE_ID/contacts?limit=100&cursor=NEXT_CURSOR" \
  -H "Authorization: Bearer ign_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Errors

Errors share one shape. code is stable and safe to branch on; message is for people and may change; requestId identifies the request when you contact support.

Error body
{
  "error": {
    "code": "api_key.write_not_supported",
    "message": "API keys are read-only in version 1.",
    "requestId": "req_01HX9Z3KQ7M2"
  }
}
Loading the reference…

Webhooks

A webhook sends an HTTP POST to your HTTPS URL when something happens in your workspace. Create one in Settings → Developer, choose the events, and copy the signing secret shown once.

Respond with any 2xx status within 10 seconds. Anything else, or no answer, counts as a failure and is retried up to 9 times with exponentially growing waits, starting at 5 minutes. A webhook that keeps failing is disabled automatically after 3 failed deliveries and the workspace's admins are notified.

The same event can be delivered more than once (retries, or your server answering after a timeout). Use the event's id to ignore a delivery you have already handled.

Always verify the signature before trusting a delivery, using the raw body exactly as received, and reject deliveries whose timestamp is more than 5 minutes old.

Headers sent with every delivery

HeaderDescription
X-Ignichat-SignatureHex HMAC-SHA256 of <timestamp>.<raw body> using the subscription's signing secret.
X-Ignichat-TimestampWhen the delivery was signed, in milliseconds since the Unix epoch. Reject deliveries older than five minutes.
X-Ignichat-EventThe event type, e.g. contact.created.

Body

application/json
{
  "id": "evt_01HX9Z3KQ7M2",
  "type": "contact.created",
  "occurredAt": "2026-10-09T10:15:30.000Z",
  "payload": {
    "contactId": "c_123"
  }
}

Verify a signature

Node.js
const crypto = require("node:crypto");

// Use the RAW request body (the exact bytes received), not a re-serialised object.
function isValid(secret, timestamp, rawBody, signature) {
  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${timestamp}.${rawBody}`)
    .digest("hex");
  const a = Buffer.from(expected);
  const b = Buffer.from(signature);
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

// Also reject stale deliveries: Math.abs(Date.now() - Number(timestamp)) > 5 * 60 * 1000

console.log(isValid(process.env.SECRET, process.env.TIMESTAMP, process.env.BODY, process.env.SIGNATURE) ? "valid" : "invalid");

Test your code with this example: the secret is not a real one.

Secret:
whsec_example_secret_0123456789
Timestamp:
1760000000000
Body:
{"id":"evt_01HX9Z3KQ7M2","type":"contact.created","occurredAt":"2026-10-09T10:15:30.000Z","payload":{"contactId":"c_123"}}
Expected signature:
9b4c3072d5762792f7f0880fe278ea8f6800d4bae8dc467a7a8de9c6b3c6c9e1

Events

EventDescriptionPayload fields
contact.createdA contact was created, from any source.contactId, name, phone, email, channelId, channelType
contact.updatedA contact's details changed.contactId
contact.tag.addedA tag was added to a contact.contactId, tagId
contact.deletedA contact was deleted.contactId
conversation.openedA conversation was opened with a contact.conversationId, contactId
conversation.assignedA conversation was assigned to a team member, or unassigned.conversationId, assigneeUserId, changedByUserId, previousAssigneeUserId
conversation.closedA conversation was resolved.conversationId
message.receivedA customer message arrived.messageId, conversationId, automationRoute, targetWorkflowId, campaignId
message.sentA message or internal note was sent from IgniChat.messageId, conversationId, visibility, mentionedUserIds
message.delivery.updatedThe delivery status of an outgoing message changed (sent, delivered, read, failed).messageId, conversationId, status
campaign.completedA campaign finished sending.campaignId, sentCount, excludedCount, failedCount
campaign.recipient.sentA campaign message was handed to the channel.campaignId, recipientId, contactId, messageId
campaign.recipient.deliveredA campaign message reached the recipient's phone.campaignId, recipientId, contactId
campaign.recipient.readA campaign message was read.campaignId, recipientId, contactId
campaign.recipient.failedA campaign message could not be sent.campaignId, recipientId, contactId, reason
campaign.recipient.repliedA recipient replied to, or tapped a button on, a campaign message.campaignId, contactId, conversationId, messageId, providerEventId, occurredAt, interactionType, quickReplyId, quickReplyTitle

Versioning

The version is part of the path (/v1). Within a version we only make additive changes: new operations, new optional parameters, new response fields and new webhook event types. Write your client to ignore fields it does not know.

A breaking change ships as a new version (/v2). The old version keeps working for at least 180 days after the announcement; during that time deprecated operations answer with Deprecation and Sunset headers.

Every change to the public API is recorded in the changelog below.

Changelog

Version 1.0.0 — 2026-10-12

  • First published version of the public API.
  • Read operations for contacts, tags, WhatsApp templates, campaigns and headline analytics.
  • Signed webhooks for every domain event, with retries and automatic disabling after repeated failure.
  • Sandbox keys (ign_test_) for trying the API without touching live data.