Skip to main content

Webhooks

TapToChat can send an HTTPS request to your server whenever something happens: a new conversation, a message, an assignment. Each request is signed, so you can tell it came from us.

Setting up a webhook

Webhooks are part of the Pro plan. Owners and admins manage them in Settings.

  1. Open Settings → Webhooks and choose Add webhook.
  2. Give it a name and the https URL that should receive events.
  3. Pick the events to send.
  4. Copy the signing secret. It is shown only once; you can rotate it later.
Open Settings → Webhooks

Events

Each webhook receives only the events it subscribes to.

EventSent when
conversation.createdA visitor starts a conversation, on any channel.
conversation.assignedA conversation is assigned to an agent.
conversation.closedA conversation is closed.
message.createdA message is stored: from a visitor, an agent, or an automatic answer.
tag.addedA tag is added to a conversation.
note.createdAn agent adds an internal note, or runs a command such as /summary, whose result is kept as a note (kind command_result).
integration.connectedAn integration is connected.
integration.disconnectedAn integration is disconnected.
integration.errorAn integration sync fails.
webhook.testYou choose Send test event in Settings. Never sent otherwise.

Payload

Every request is a POST with a JSON body in the same envelope:

{
  "id": "6f1c2b0e-8a7d-4c1e-9b3a-2d5e7f9a1c4b",
  "event": "message.created",
  "created_at": "2026-10-01T09:30:00.000Z",
  "organization_id": "0c9a1f4e-3b2d-4e5f-8a6b-7c8d9e0f1a2b",
  "data": {
    "message": {
      "id": "a1b2c3d4-…",
      "conversation_id": "e5f6a7b8-…",
      "sender_type": "visitor",
      "sender_id": null,
      "content": "Hello, is anyone there?",
      "channel": "web",
      "created_at": "2026-10-01T09:30:00.000Z"
    },
    "conversation": { "id": "e5f6a7b8-…", "status": "open", "visitor_name": "Ada", "…": "…" }
  }
}
  • id: the event id. A retried delivery carries the same id, so use it to ignore duplicates.
  • event: the event name, as in the table above.
  • data: what happened. Its fields depend on the event; a message event carries the message and its conversation.

Headers

  • X-TapToChat-Signature: the signature, v1= followed by a hex HMAC-SHA256.
  • X-TapToChat-Timestamp: when the request was signed, in Unix seconds.
  • X-TapToChat-Event: the event name.
  • X-TapToChat-Delivery-Id: this delivery. Every retry of it carries the same id.

Verifying the signature

Compute an HMAC-SHA256 of the timestamp, a full stop and the raw request body, keyed with your signing secret, and compare it with the signature header in constant time. Refuse requests whose timestamp is more than 5 minutes away from your clock, so a captured request cannot be replayed.

Use the whole secret, including its whsec_ prefix, as the key. Verify the raw body before parsing it: re-serialised JSON will not match.

import crypto from 'node:crypto'

// The raw request body, exactly as received: verify before parsing JSON.
export function verifyTapToChat(rawBody, headers, secret) {
  const timestamp = headers['x-taptochat-timestamp']
  const signature = headers['x-taptochat-signature']
  if (!timestamp || !signature) return false

  // Refuse old requests, so a captured one cannot be replayed later.
  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false

  const expected =
    'v1=' + crypto.createHmac('sha256', secret).update(`${timestamp}.${rawBody}`).digest('hex')
  const a = Buffer.from(signature)
  const b = Buffer.from(expected)
  return a.length === b.length && crypto.timingSafeEqual(a, b)
}

Retries

Answer with any 2xx status within 10 seconds to acknowledge a delivery. Otherwise it is retried, up to 6 attempts in all.

The waits between attempts are 30s, 60s, 120s, 240s, 480s.

A 4xx answer other than 408 or 429 stops the retries at once, and so does a redirect: redirects are not followed, so give the final URL.

A delivery can occasionally arrive more than once. Use the id in the body to process each event once.

Automatic switch-off

After 20 deliveries in a row fail, the webhook is switched off and Settings shows why. Fix the receiver, then switch it back on; the count starts again.

Good to know

  • The URL must use https and point to a public address.
  • Messages that agents send from the TapToChat mobile app do not raise message.created yet. Messages sent from the web dashboard and every channel do.

Something not working as described? Contact us.