Developers
Webhooks
Send issue, comment, message and project events to Zapier, Make, n8n or your own services as they happen, signed so you can verify them.
Webhooks tell other tools about changes in Deckbell as they happen: Deckbell
sends an HTTPS POST with a JSON body to a URL you choose. This is how
Zapier, Make, n8n and your own services react to new issues, comments and
messages.
Workspace owners and admins manage webhooks in Settings → Webhooks.
Add a webhook
- Open Settings → Webhooks and choose Add webhook.
- Paste the URL to send events to. It must be a public
https://address; local and private network addresses aren't allowed. - Tick the events to send, or All events (which also includes events added later).
- Copy the signing secret (it starts with
whsec_). You can reveal it again in the webhook's details, and Rotate secret replaces it.
Choose Send test in the webhook's details to send a ping event and see
what your endpoint answered.
Events
| Event | Sent when | data contains |
|---|---|---|
issue.created | An issue is created | issue |
issue.updated | Its title, description, status, priority, assignee, labels, estimate, parent, sprint or archived state changes | issue |
issue.deleted | An issue is deleted | issue (as it was) |
comment.created | Someone comments on an issue | comment, issue |
message.created | A message is posted in a public channel | message, channel |
project.created | A project is created | project |
project.updated | A project is renamed or edited | project |
project.archived, project.restored | A project is archived or restored | project |
project.deleted | A project is deleted | project (as it was) |
Events are sent whatever made the change: someone in the app, the API, an AI assistant over MCP, or an agent. Messages in private channels and direct messages are never sent. Issues brought in by an import don't send one event each.
The objects in data have the same fields as in the REST API.
Payload
{
"id": "evt_01jbx3m8k2…",
"type": "issue.updated",
"created_at": "2026-10-29T12:00:00+00:00",
"workspace": { "id": 7, "name": "Acme", "slug": "acme" },
"actor": { "id": 12, "name": "Dana Dev", "username": "dana-dev", "avatar": null },
"data": {
"issue": { "identifier": "WEB-42", "title": "Checkout button is broken", "state": { "name": "In Progress", "category": "started" }, … }
},
"changes": {
"state": { "from": { "id": 3, "name": "Todo", "category": "unstarted" }, "to": { "id": 4, "name": "In Progress", "category": "started" } },
"priority": { "from": "none", "to": "high" },
"labels": { "added": [{ "id": 9, "name": "Bug" }], "removed": [] }
}
}
actoris who made the change, ornullfor agents and automatic changes.changesis only on*.updatedevents. Several edits made together arrive as one event.idis the same on every retry, so you can skip events you've already handled.
Each request also has these headers:
Deckbell-Event: the event type, e.g.issue.createdDeckbell-Delivery: a unique id for the deliveryDeckbell-Signature: the signature (below)
Verify the signature
Deckbell-Signature looks like t=1730203200,v1=5257a869…. v1 is an
HMAC-SHA256 of the timestamp, a dot and the raw request body, keyed with your
signing secret. Check it before trusting a request, and reject old timestamps
(say, more than 5 minutes) to stop replays.
Node.js:
import crypto from 'node:crypto';
function verify(rawBody, header, secret) {
const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')));
const expected = crypto.createHmac('sha256', secret).update(`${parts.t}.${rawBody}`).digest('hex');
const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300;
return fresh && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1 ?? ''));
}
Python:
import hmac, hashlib, time
def verify(raw_body: bytes, header: str, secret: str) -> bool:
parts = dict(p.split("=", 1) for p in header.split(","))
expected = hmac.new(secret.encode(), f"{parts['t']}.".encode() + raw_body, hashlib.sha256).hexdigest()
return abs(time.time() - int(parts["t"])) < 300 and hmac.compare_digest(expected, parts.get("v1", ""))
Use the body exactly as received: parsing and re-encoding the JSON changes it and the signature won't match.
Retries and failures
Answer with any 2xx status within 10 seconds. Anything else (or no answer)
counts as a failure, and Deckbell tries again after 1 minute, 5 minutes,
30 minutes, 2 hours, 6 hours and 12 hours. Redirects aren't followed.
If 15 deliveries in a row fail every attempt, Deckbell turns the webhook off and notes why in its details (and in the audit log). Fix the endpoint, then switch the webhook back on.
Delivery log
Open a webhook to see its recent deliveries from the last 30 days: the event, whether it was delivered, the response code, and how many attempts it took. Select one to see the exact payload and your endpoint's response, and Redeliver to send it again.
Zapier, Make and n8n
Use the tool's "catch webhook" trigger (Zapier: Webhooks by Zapier → Catch Hook; Make: Webhooks → Custom webhook; n8n: Webhook node), copy the URL it gives you into a new Deckbell webhook, and choose Send test so the tool learns the payload's fields. To make changes in Deckbell from those tools, call the REST API with an API token.