Viewing generic examples.Sign into see your own apps and ids in every sample.

Chat API & Webhooks

Two ways to wire conversations into your own software: talk to the assistant directly over HTTP (drive it from your own UI, test prompts from CI, let another system converse with it), and be told when conversations finish via a webhook (post transcripts to your CRM, trigger follow-ups, feed analytics).

Available to any signed-in member of the account that owns the app, and to scoped API keys with the chat capability. Runs a synchronous chat exchange against the app's prompt. Three fields are required: conversationId (your identifier for the conversation — reuse it to continue the same conversation), userIdentifier (a stable identifier for the end user, e.g. their email or your user id — this is what links the conversation to a Contact record), and message.

Response (200):

Send the same conversationId on subsequent calls to maintain conversation context.

This endpoint waits for the complete answer before responding, and its gateway allows up to 60 seconds. For conversations that use tools (which can take 30 seconds or more per turn), we recommend the streaming variant below — you see the answer as it is generated, exactly like the dashboard preview.

POST/api/v1/apps/{appId}/chat
curl -X POST https://api.ilq.ai/api/v1/apps/$APP_ID/chat \
  -H 'Authorization: Bearer YOUR_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{
    "conversationId": "conv-12345",
    "userIdentifier": "customer@example.com",
    "message": "Hello, can I book an appointment?"
  }'
Sign in to run this against your account.
JSON
{
  "conversationId": "conv-12345",
  "contactId": "...",
  "userMessage": "Hello, can I book an appointment?",
  "aiResponse": "Of course! What date and time works for you?"
}

Streaming chat uses a dedicated endpoint (not the api.ilq.ai host) with the SAME Authorization header you already use — your API token or scoped key (chat capability, pinned to its own app):

The response is a standard Server-Sent Events stream. Text arrives as it is generated:

Notes:

  • query is a JSON document held as a string, with the user's message under question.
  • Reuse conversationId to continue a conversation; messages are persisted and the conversation appears in the dashboard exactly like one from the buffered endpoint, linked to the Contact identified by userIdentifier.
  • Errors arrive as event: error frames with a JSON body (401 invalid token, 403 wrong app or account, 429 rate limited — the streaming endpoint allows up to 30 conversation starts per minute per credential).
  • Comment frames (lines starting :) are heartbeats during long tool runs; SSE parsers ignore them automatically.
  • In JavaScript, read the response body with fetch and a ReadableStream reader (the EventSource API only supports GET); in Python, use requests with stream=True and iterate lines.
curl -N -X POST https://vixx7dopyco6rkjo2h4dqq7exq0byvxo.lambda-url.eu-west-1.on.aws/ \
  -H 'Authorization: Bearer YOUR_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{
    "appId": "'$APP_ID'",
    "targetPath": "/'$APP_ID'",
    "conversationMode": true,
    "conversationId": "conv-12345",
    "userIdentifier": "customer@example.com",
    "isStreamingFormat": true,
    "query": "{\"question\": \"Hello, can I book an appointment?\"}"
  }'
text
event: message
data: Of course!

event: message
data:  What date and time

event: message
data:  works for you?

Apps can configure a postChatCallback attribute that fires after each completed conversation. Useful for:

  • Posting transcripts to your CRM
  • Triggering follow-up automation
  • Custom analytics

Set the callback by PATCHing the app:

Payload (POSTed to your URL after each call):

JSON
{
  "attributes": {
    "postChatCallback": {
      "uri": "https://your-server.com/webhook",
      "method": "POST",
      "headers": { "Authorization": "Bearer YOUR_INTERNAL_TOKEN" }
    }
  }
}
JSON
{
  "appId": "...",
  "chatId": "...",
  "callerIdPhone": "+44...",
  "callbackPhone": "+44...",
  "callerName": "...",
  "callerEmail": "...",
  "summary": "Caller booked an appointment for...",
  "urgency": "normal",
  "sentiment": 8,
  "callDuration": 145,
  "transcript": [ { "speaker": "...", "text": "...", "timestamp": "..." } ],
  "patientContext": { /* populated when the Semble integration is connected */ },
  "bookingsMade": [ /* if a booking integration is connected */ ]
}

Timeout: each delivery attempt times out after 5 seconds. Your endpoint should acknowledge with a 2xx response inside that window — even a bare 200 OK is fine; we don't need a body. Slow processing (writing to your CRM, triggering downstream automations) should happen asynchronously after you've already responded; treating webhook delivery as an inbox-write pattern is the safest shape.

Retry policy: failed deliveries (any non-2xx response, connection refused, or timeout) are retried with exponential backoff:

Attempt Delay after previous
1 immediate (first try)
2 ~30 seconds
3 ~2 minutes
4 ~10 minutes
5 ~1 hour
6 (final) ~6 hours

After 6 failed attempts, the platform stops trying for that conversation and records the failure internally. We do not notify your account by default; if you need failure notifications, talk to support.

Idempotency: every delivery carries the same (appId, chatId) tuple — if you receive the same payload twice (e.g. a retry fired even though your endpoint succeeded but the connection dropped before we read the response), de-duplicate on chatId. Payload content never changes between retries, so a second delivery for a known chatId is safe to drop.

Signature verification: the platform signs each request with HMAC-SHA256 over the JSON body, included as an X-ILQ-Signature header. The signing key is generated when you configure the callback and returned as the webhookSigningSecret field in the PATCH /apps/{id} response that sets postChatCallback (it starts with whsec_); store it server-side. If you set the callback up before signing was available, re-save it once (any PATCH that sets postChatCallback) to generate the key — deliveries stay unsigned, exactly as before, until you do. Verify on receipt:

Without verification you can't tell a real platform delivery from a third party who's discovered your callback URL. We strongly recommend verifying every payload.

Failure-mode summary:

Scenario What happens
Your endpoint returns 2xx Delivery succeeded; no retry.
Your endpoint returns 4xx Treated as permanent failure; retries cease after attempt 1. (Reason: 4xx usually means malformed payload from our side; retrying won't help.)
Your endpoint returns 5xx Retried per the backoff table above.
Connection refused / DNS failure Retried per the backoff table above.
Timeout (>5s, no response) Retried per the backoff table above.
All 6 retries exhausted Recorded on our side; not delivered to your account.

The call itself succeeds regardless of webhook outcome. Webhook delivery is asynchronous to the call; a failed webhook never affects the caller's experience or your billing. The transcript + summary + analytics are still stored in your account and queryable via the /conversations endpoints — the webhook is a convenience, not the source of truth.

Other events: the post-call webhook is the only customer-subscribable push notification today. For billing and usage events, poll GET /api/v1/billing/merchant or the ledger endpoints on your own schedule. A general-purpose subscribable event stream is on the roadmap but not shipped.


python
import hmac, hashlib
expected = hmac.new(signing_key.encode(), request.body, hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected, request.headers.get('X-ILQ-Signature', '')):
    return 401