A webhook is Trawpie calling you: when something happens in a team, it sends a short, signed JSON message to an address of yours, within seconds. Use it to post to a channel, update a spreadsheet, start a build or keep another tool in step.
Add one
- Get an addressAny public https address that accepts a POST. Zapier: Webhooks by Zapier ▸ Catch Hook. Make: Webhooks ▸ Custom webhook. n8n: a Webhook node set to POST. Or your own server.
- Add it in TrawpieSettings ▸ Integrations ▸ Webhooks ▸ Add a webhook. Paste the address and choose the events (all, or some). You need to manage the team’s settings.
- Copy the secretTrawpie shows the webhook’s secret (
whsec_…) once. Your receiver uses it to check each message came from Trawpie. - Send a testSend a test posts a
pingevent now and says what your address answered. Recent deliveries lists what was sent.
task.completed. When anyone finishes a task, Zapier adds a row to the team’s “Done this term” sheet with the task, who did it and the week.Events
| Event | Sent when |
|---|---|
task.created | A task is added. A task carried forward into a new week arrives as a new task with the same ref and a carried_from. |
task.updated | Its title, owner, week, row, dates, priority, status text, stage, helpers or sub-tasks change. |
task.completed | It’s marked completed. |
task.reopened | A completed task is opened again. |
task.deleted | It’s deleted. |
comment.created | Someone comments on a task. |
ping | You press Send a test (always sent, whatever you chose). |
One change sends one event: finishing a task sends task.completed, not task.updated as well. Moving cards around on the board, without changing anything else, sends nothing.
What arrives
A POST with a JSON body and these headers:
| Header | Holds |
|---|---|
Trawpie-Event | The event, such as task.completed. |
Trawpie-Delivery | This delivery’s id (the same on each retry). |
Trawpie-Signature | t=<unix seconds>,v1=<signature>: see below. |
Trawpie-Attempt | 1 for the first try, 2 for the first retry, and so on. |
{
"id": "evt_5f2c1a9d0b7e4c3a8d6f1e2b",
"type": "task.completed",
"created_at": "2026-11-11T14:32:05.000Z",
"team": { "id": "t_7c1e9a", "name": "Robotics" },
"data": {
"task": {
"id": "t_4be09f12a3c5d7e9f011", "ref": "TT-LNWZM6", "title": "Order motor drivers",
"owner_uid": "u_sam", "owner": "Sam Taylor", "category_id": "c_hw", "category": "Hardware",
"week_key": "wk_3", "week": "WEEK 4", "flag": "completed", "priority": "Major", "status": "",
"due_date": "2026-11-13", "start_date": null, "completed_date": "2026-11-11", "stage": null,
"subtasks": { "done": 2, "total": 2 }, "updated_by": "Sam Taylor",
"updated_at": "2026-11-11T14:32:04.000Z",
"link": "https://trawpie.org/#/t/t_7c1e9a/task/t_4be09f12a3c5d7e9f011"
},
"changes": {
"flag": { "from": "none", "to": "completed" },
"completed_date": { "from": null, "to": "2026-11-11" }
}
}
}changes lists each field that changed, from and to (not in task.created or task.deleted). comment.created sends data.comment (id, task_id, author, text, created_at) and the task it’s on.
Check the signature
Anyone could post to your address, so check each message. Trawpie signs <t>.<the body exactly as sent> with HMAC-SHA256 and the webhook’s secret. Recompute it from the raw body (before any JSON parsing), compare, and refuse messages more than 5 minutes old.
import crypto from 'node:crypto';
// raw: the body exactly as received (string or Buffer); header: the Trawpie-Signature header
function fromTrawpie(raw, header, secret) {
const parts = Object.fromEntries(String(header).split(',').map(p => p.split('=')));
const t = Number(parts.t);
if (!t || Math.abs(Date.now() / 1000 - t) > 300) return false; // over 5 minutes old: refuse
const want = crypto.createHmac('sha256', secret).update(t + '.' + raw).digest('hex');
return want.length === (parts.v1 || '').length && crypto.timingSafeEqual(Buffer.from(want), Buffer.from(parts.v1));
}import hmac, hashlib, time
def from_trawpie(raw: bytes, header: str, secret: str) -> bool:
parts = dict(p.split("=", 1) for p in header.split(","))
t = int(parts.get("t", "0"))
if abs(time.time() - t) > 300: # over 5 minutes old: refuse
return False
want = hmac.new(secret.encode(), f"{t}.".encode() + raw, hashlib.sha256).hexdigest()
return hmac.compare_digest(want, parts.get("v1", ""))Answer quickly, and expect repeats
- Answer with any 2xx status within 10 seconds, then do the slow work. Anything else (or no answer) counts as failed.
- Redirects aren’t followed: give the final address.
- A failed delivery is tried again after 1, 5, 30, 120 and 360 minutes (6 tries over about 8½ hours). The event’s
idstays the same, so use it to ignore one you’ve already handled. - Events can arrive out of order. Compare
updated_at, or fetch the task from the REST API when order matters.
When an address keeps failing
If every delivery to an address fails for a day, Trawpie pauses the webhook and says so in Settings ▸ Integrations ▸ Webhooks. Fix the receiver, press Send a test, then switch it back on. Events from while it was paused aren’t sent later.
From the API
People who manage a team’s settings can do all of this with the REST API too: GET and POST /teams/{team_id}/webhooks, PATCH or DELETE …/webhooks/{id} ({"on": false} pauses one), POST …/webhooks/{id}/test and POST …/webhooks/{id}/rotate-secret.