Help center · For AI teams
Webhooks and the signature
Which events Canonset's delivery webhook sends, what each carries, how to verify the Canonset-Signature header, how long you have to answer, and how failed deliveries are retried.
Your company can register one HTTPS endpoint, and Canonset POSTs a small signed JSON event when something you picked happens: an answer is approved, or a project is completed. Events carry IDs, never answers; pull those through the API. Each delivery is signed with a secret shown to you once.
Setting it up
On API in your client account, enter an https:// address on your side that accepts POST requests, and tick the events you want:
answer.approved: an answer is approved and ready to export.project.completed: a project is completed.
Saving a new endpoint shows its signing secret once; copy it then. You can roll the secret, send a test event, and remove the webhook (its delivery log goes with it). The URL must point to a public server over https, with no credentials in it: verify our signature instead.
What an event looks like
Each event is JSON: {"id", "type", "created", "data"}.
answer.approveddata:project_id,task_id,approved_at.project.completeddata:project_id,completed_at.- A test event has the type
pingand a short message.
Three headers come with it: Canonset-Signature (below), Canonset-Event (the event's type) and Canonset-Delivery (the event's ID). Use Canonset-Delivery to ignore repeats. Events can arrive out of order.
Verifying the signature
Canonset-Signature is t=<unix seconds>,v1=<hex HMAC-SHA256 of "<t>.<raw body>" with your secret>, the same scheme as Stripe's. Compute the HMAC over the timestamp, a dot and the raw request body exactly as received, compare it with v1 in constant time, and reject timestamps more than 5 minutes from your clock. In Node.js:
const [t, v1] = req.headers["canonset-signature"].split(",").map((p) => p.split("=")[1]);
const expected = crypto.createHmac("sha256", process.env.CANONSET_WEBHOOK_SECRET)
.update(`${t}.${rawBody}`).digest("hex");
const fresh = Math.abs(Date.now() / 1000 - Number(t)) < 300;
const valid = fresh && v1?.length === expected.length && crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(expected));Deliveries are signed with the secret current when they are sent, so after you roll the secret, new deliveries and retries use the new one.
Answering
Answer with any 2xx status within 10 seconds. We don't follow redirects. A timeout or any other status counts as a failed delivery.
Retries and the delivery log
A failed delivery is retried after 1, 5, 30, 120 and 720 minutes. After the last retry it stays failed until you press Send again on the API page, which sends it now. When a delivery gives up after its last retry, everyone at your company is emailed that we couldn't reach your endpoint, with what it said, at most once a day (not for test events).
The API page lists your last 20 deliveries with the event, its status, the HTTP status your endpoint returned, the number of tries and the last error. The log keeps 30 days.
Deleting all of your company's data also removes the webhook. See The Canonset API for the endpoints the events point you to.
Questions: support@canonset.com.
Updated Nov 11, 2026.