TrawpieDocs Guide Open Trawpie
AI agents and API

Webhooks

Webhooks send a signed JSON message to your https address when tasks are added, changed, completed, reopened or deleted, or someone comments. Events, payloads, checking the signature, retries.

Updated 11 Nov 2026 · 5 min read

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

  1. 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.
  2. 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.
  3. Copy the secretTrawpie shows the webhook’s secret (whsec_…) once. Your receiver uses it to check each message came from Trawpie.
  4. Send a testSend a test posts a ping event now and says what your address answered. Recent deliveries lists what was sent.
Example
Sam’s team adds a Zapier address for 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

EventSent when
task.createdA 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.updatedIts title, owner, week, row, dates, priority, status text, stage, helpers or sub-tasks change.
task.completedIt’s marked completed.
task.reopenedA completed task is opened again.
task.deletedIt’s deleted.
comment.createdSomeone comments on a task.
pingYou 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:

HeaderHolds
Trawpie-EventThe event, such as task.completed.
Trawpie-DeliveryThis delivery’s id (the same on each retry).
Trawpie-Signaturet=<unix seconds>,v1=<signature>: see below.
Trawpie-Attempt1 for the first try, 2 for the first retry, and so on.
task.completed
{
  "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.

Node.js
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));
}
Python
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 id stays 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.

Trawpie sends only to public https addresses (port 443 or 8443): never to a private network, this computer or a cloud’s internal addresses, and it checks that again when it connects. Each team can have 5 webhooks. A new secret stops the old one working straight away.