Webhooks
Receive signed CanWeChat events in your own backend.
Headers
X-CanWeChat-Id, X-CanWeChat-Timestamp, and X-CanWeChat-Signature: v1=<hex>. The signed string is timestamp.raw-json-body using HMAC-SHA256.
Verify
Compute HMAC-SHA256 of `${timestamp}.${rawBody}` with the signing secret shown once at creation or rotation. Compare to the hex after v1=. Reject if the timestamp is too old for your tolerance.
import { createHmac, timingSafeEqual } from "node:crypto";
function verify(rawBody, timestamp, signatureHeader, secret) {
const expected = "v1=" + createHmac("sha256", secret).update(`${timestamp}.${rawBody}`).digest("hex");
const a = Buffer.from(expected);
const b = Buffer.from(signatureHeader);
return a.length === b.length && timingSafeEqual(a, b);
}Payload
Events include conversation.created and message.created. Payloads include conversation IDs, visitor identity when provided, page context, message source, and timestamps. They do not include integration tokens or auth secrets.
Example event
Store the event ID before processing so retries do not create duplicate records in your system.
{
"id": "evt_01J7...",
"type": "message.created",
"createdAt": "2026-09-10T09:20:00.000Z",
"conversation": {
"id": "conv_01J7...",
"status": "open",
"pageUrl": "https://example.com/pricing"
},
"visitor": {
"id": "vis_01J7...",
"name": "Ada Lovelace",
"email": "ada@example.com"
},
"message": {
"id": "msg_01J7...",
"senderType": "visitor",
"source": "widget",
"body": "Do you support idempotency keys?"
}
}Retries
Failed deliveries retry from a Cloudflare Queue. Delivery history records event type, delivery status, HTTP code, attempt number, timestamp, and a bounded response snippet.
Use the dashboard delivery log to confirm that your endpoint returned a 2xx response. Rotate the signing secret if it is exposed, then update your backend before sending another test event.