Skip to content

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.

POST /your/endpoint HTTP/1.1
Content-Type: application/json
User-Agent: RolChat-Webhook/1.0
X-RolChat-Timestamp: 1713615825
X-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 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.

Do these five checks in order. Samples in Python, Node.js, Go, Ruby and PHP are on the verification samples page.

  1. Freshness. Reject if |now − X-RolChat-Timestamp| > 300 seconds (400). Five minutes tolerates clock skew and makes a captured request worthless after that. Keep your server on NTP.
  2. Signature. Compute the expected value and compare with a constant-time comparison (hmac.compare_digest, crypto.timingSafeEqual, hash_equals). Reject with 401 on mismatch. A plain string comparison leaks timing and is not acceptable.
  3. Nonce. Store X-RolChat-Nonce with a 10-minute TTL using an atomic set-if-absent (SET NX EX 600). If it already exists, reject with 409: the request is a replay. If your nonce store is down, fail closed with 503 so RolChat retries; never fall through to “accept”.
  4. Parse the body only now.
  5. 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, answer 200 without processing again.

Answer 2xx as soon as the delivery is safely accepted; do the work asynchronously. RolChat waits 15 seconds for a response.

  • 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/rotate both 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/revoke invalidates it immediately, issues a new one, e-mails the account admins and writes an audit entry.

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).

  • 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.0 user agent as an extra sanity check. It is not a security control.

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.

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, 425 and 429. Other 4xx answers, 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.

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.

  • 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.