Webhooks
RolChat delivers events to your systems as signed HTTP POST requests. This page describes plugin webhooks; webhooks sent by an Automation rule differ in retries, waiting time and secret rotation, see Automation webhooks. The same rules apply in the other direction when your system, a plugin or a provider posts into RolChat. Verify every delivery in the order below before you act on it.
What a delivery looks like
Section titled “What a delivery looks like”POST /your/endpoint HTTP/1.1Content-Type: application/jsonUser-Agent: RolChat-Webhook/1.0X-RolChat-Timestamp: 1713615825X-RolChat-Signature: sha256=a1b2c3…X-RolChat-Nonce: nonce_01HKQN9…X-RolChat-Event-Id: evt_01HKQN9…
{ "id": "evt_01HKQN9…", "type": "conversation.created", "occurred_at": "…", "data": { … } }| Header | Meaning |
|---|---|
X-RolChat-Timestamp |
Unix seconds at dispatch. Part of the signed message. |
X-RolChat-Signature |
sha256=<hex>. The prefix names the algorithm so it can change later. |
X-RolChat-Nonce |
A ULID unique to this HTTP request. A retry gets a new nonce. |
X-RolChat-Event-Id |
The event’s own ID. Stable across retries; use it to deduplicate. |
Outbound deliveries go to HTTPS endpoints only. An HTTP URL is rejected when the subscription is created.
The signature
Section titled “The signature”The signed message is the timestamp, a literal dot, and the raw request body, byte for byte:
canonical = "<timestamp>.<body>"signature = hex( HMAC-SHA256( secret, canonical ) )Sign the bytes you received, not a re-serialised copy of the parsed JSON. Whitespace or key order changes break the signature. SHA-256 is the only accepted algorithm.
Verifying a delivery
Section titled “Verifying a delivery”Do these five checks in order. Samples in Python, Node.js, Go, Ruby and PHP are on the verification samples page.
- Freshness. Reject if
|now − X-RolChat-Timestamp| > 300seconds (400). Five minutes tolerates clock skew and makes a captured request worthless after that. Keep your server on NTP. - Signature. Compute the expected value and compare with a constant-time comparison (
hmac.compare_digest,crypto.timingSafeEqual,hash_equals). Reject with401on mismatch. A plain string comparison leaks timing and is not acceptable. - Nonce. Store
X-RolChat-Noncewith a 10-minute TTL using an atomic set-if-absent (SET NX EX 600). If it already exists, reject with409: the request is a replay. If your nonce store is down, fail closed with503so RolChat retries; never fall through to “accept”. - Parse the body only now.
- Deduplicate by event ID. Retries carry a new nonce but the same
X-RolChat-Event-Id. If you have processed that ID in the last seven days, answer200without processing again.
Answer 2xx as soon as the delivery is safely accepted; do the work asynchronously. RolChat waits 15 seconds for a response.
Secrets
Section titled “Secrets”- A webhook secret is 64 random bytes, base64url-encoded (about 86 characters). It is generated when the subscription or plugin is created and shown once.
- Store it in your own vault or environment; RolChat keeps only an encrypted copy.
- Rotation is recommended after 90 days; the product shows a reminder but never disables a subscription for not rotating.
- Rotation uses an overlap window: after
POST /api/v1/plugins/{id}/webhook-secret/rotateboth the old and the new secret verify for seven days, new deliveries are signed with the new one, and the old one is deleted after the window. Nothing breaks the moment you rotate. - If a secret leaks,
POST /api/v1/plugins/{id}/webhook-secret/revokeinvalidates it immediately, issues a new one, e-mails the account admins and writes an audit entry.
Retries and dead letters
Section titled “Retries and dead letters”A non-2xx response or a network failure schedules a retry:
| Attempt | Delay after the previous one |
|---|---|
| 1 | immediately |
| 2 | 15 seconds |
| 3 | 1 minute |
| 4 | 5 minutes |
| 5 | 30 minutes |
| 6 | 2 hours |
| 7 | 8 hours |
| 8 | 24 hours |
408, 429, 5xx and network errors are retried. 400, 401, 403, 404 and 410 are not: the delivery goes straight to the dead-letter queue, because retrying a rejected signature or a missing endpoint cannot help. After eight failed attempts the delivery is dead-lettered too.
Dead letters are kept for 30 days and are visible in Settings → Plugins → {plugin} → Webhook log → DLQ, where an admin can replay one (POST /api/v1/plugins/{id}/webhook-dlq/{entry_id}/replay).
Limits
Section titled “Limits”- Outbound body: 1 MB by default (configurable per plugin). Inbound body into RolChat: 5 MB. Larger is
413. - A plugin can declare
allowed_source_ips; RolChat applies that allowlist before verifying. - You can pin on the
RolChat-Webhook/1.0user agent as an extra sanity check. It is not a security control.
Inbound webhooks into RolChat
Section titled “Inbound webhooks into RolChat”Providers post to RolChat at /api/v1/webhooks/{category}/{adapter} using their own signature schemes (Twilio, Telnyx, Bandwidth, Meta for WhatsApp, Stripe). Each adapter verifies its provider’s signature, normalises the payload and republishes it on the internal event bus with a RolChat signature. Your own system can post to RolChat with the same X-RolChat-* headers described above, signed with the secret of the plugin that owns the endpoint.
Automation webhooks
Section titled “Automation webhooks”The Send HTTP request action of the Automation module has its own delivery rules:
- One secret per account. Show or rotate it on the Automation screen (Owner and Admin). Rotation takes effect at once: there is no overlap window, the old secret stops verifying immediately, so update your receiver first or accept a short gap.
- Waiting time: RolChat waits 10 seconds for a response.
- Retries: up to 6 attempts in total. The pause before attempt 2 is 30 seconds, then about 2 minutes, 8 minutes, 32 minutes and 2 hours 8 minutes, each varied by up to 20 %. A single pause never exceeds 6 hours.
- What is retried: network errors, timeouts,
5xx,408,425and429. Other4xxanswers, a blocked address or an oversized body are not retried: the delivery goes straight to the dead-letter queue, as it does after the last failed attempt. - Headers:
X-RolChat-Signature,X-RolChat-Signature-V2,X-RolChat-Timestamp,X-RolChat-Delivery-Id,X-RolChat-Attempt.
Testing your receiver
Section titled “Testing your receiver”POST /api/v1/webhooks/test-signature sends a synthetic signed event to the endpoint of the calling plugin so you can verify your implementation end to end before real traffic arrives.
Common mistakes
Section titled “Common mistakes”- Verifying against the parsed and re-encoded JSON instead of the raw bytes.
- Using
==to compare signatures. - Deduplicating by nonce (every retry has a new one) instead of by event ID.
- A clock more than five minutes off.
- Doing slow work before answering, then timing out at 15 seconds and receiving the same event again.