# Message webhooks

Subscribe to Bow Chat message events, verify webhook signatures, deduplicate deliveries, and update downstream delivery state.

## Subscribe

Create one HTTPS webhook for the integration and subscribe to `message_created` and `message_updated`:

```bash
curl --request POST \
  --url https://app.bow.chat/api/v1/accounts/ACCOUNT_ID/webhooks \
  --header 'api_access_token: BOW_CHAT_TOKEN' \
  --header 'content-type: application/json' \
  --data '{"webhook":{"name":"Delivery events","url":"https://example.com/webhooks/bow-chat","subscriptions":["message_created","message_updated"]}}'
```

Save the `secret` returned for that webhook in a server-side secret store.

## Verify every request

Bow Chat sends these headers for account webhooks:

| Header | Use |
|---|---|
| `X-Chatwoot-Delivery` | Unique delivery ID for deduplication |
| `X-Chatwoot-Timestamp` | Unix timestamp used in the signature |
| `X-Chatwoot-Signature` | `sha256=<hex HMAC>` |

Compute HMAC-SHA256 with the webhook secret over:

```text
<timestamp>.<exact raw request body>
```

Compare the result with `X-Chatwoot-Signature` using a constant-time comparison. Reject a timestamp outside your replay window, and record `X-Chatwoot-Delivery` before processing so a retry cannot produce a duplicate action. Parse JSON only after signature verification.

## Process message states

For outgoing messages, keep the latest state by `message.id`:

- `sent`: accepted by the channel path; keep waiting.
- `delivered`: delivered when the provider reports it.
- `read`: read when the provider reports it.
- `failed`: terminal for that attempt; inspect the error before deciding any fallback.

Events can be retried or arrive after your own timeout. Make processing idempotent and never let a late event send the same business notification twice.

See [Delivery and fallback](/developers/delivery-and-fallback).

Canonical: https://docs.bow.chat/developers/webhooks
Version: 1
Updated: 2026-09-04T10:04:30.595Z
