GetItDoneGetItDone Docs
Public API
Public API

Webhooks

GetItDone outbound webhooks (Pro+): the task event catalog, Standard Webhooks signature verification, delivery retries, and secret rotation.

Register an HTTPS endpoint and receive signed events when tasks change. Available on Pro and Team plans.

Event types

These event types are exactly what the delivery worker emits:

  • task.created
  • task.updated
  • task.archived

Example signed delivery

POST https://your-app.example.com/webhooks/getitdone
webhook-id: msg_2abc...
webhook-timestamp: 1753305600
webhook-signature: v1,g0hM9SsE9...base64signature...

{
  "type": "task.created",
  "data": {
    "id": "T-123",
    "title": "Ship the public API",
    "status": "TODO"
  }
}

Verify the signature

Payloads are signed with the Standard Webhooks scheme. Each request carries webhook-id, webhook-timestamp and webhook-signature headers. Always verify the raw request bytes — parsing and re-serializing the body first breaks the signature.

With the TypeScript SDK
import { verifyWebhook } from '@nowgetitdone/sdk/webhooks'

// In your route handler — pass the RAW request body bytes, never a re-parsed
// object (re-serializing breaks the signature).
const result = verifyWebhook({
    headers: request.headers,
    rawBody: await request.text(),
    secret: process.env.GETITDONE_WEBHOOK_SECRET, // whsec_… (pass both during rotation)
})

if (!result.valid) {
    return new Response('invalid signature', { status: 400 })
}

The SDK ships a verifyWebhook helper (published on npm — source).

Runtime-agnostic (HMAC-SHA256)
import { createHmac, timingSafeEqual } from 'node:crypto'

// Runtime-agnostic Standard Webhooks verification (no SDK required).
function verify(headers, rawBody, whsec) {
    const id = headers['webhook-id']
    const timestamp = headers['webhook-timestamp']
    const signatureHeader = headers['webhook-signature'] // "v1,<base64> v1,<base64> …"

    const secretBytes = Buffer.from(whsec.split('_')[1], 'base64')
    const signed = `${id}.${timestamp}.${rawBody}`
    const expected = createHmac('sha256', secretBytes)
        .update(signed)
        .digest('base64')

    return signatureHeader.split(' ').some(part => {
        const sig = part.split(',')[1]
        return (
            sig !== undefined &&
            sig.length === expected.length &&
            timingSafeEqual(Buffer.from(sig), Buffer.from(expected))
        )
    })
}

Retries & auto-disable

Deliveries that fail (non-2xx, timeout, connection error) are retried with backoff. An endpoint that keeps failing is automatically disabled to protect your receiver and our delivery pipeline; re-enable it once your endpoint is healthy again. Read the delivery log per endpoint to see attempts and their outcomes.

A disabled endpoint receives nothing, whether it was disabled automatically, by you through the API or the console, or paused because the account that set it up had its email address verified (disabled_reason says which). Deliveries already queued for it are held, not sent, and resume only if you re-enable the endpoint (a take-back pause sets its queued deliveries aside for good instead).

Secret rotation

Each endpoint has a signing secret prefixed whsec_. Rotating it keeps the previous secret valid for a 24-hour grace window so in-flight deliveries still verify. Accept both secrets during rotation, then drop the old one after the window.

URL rules

Endpoint URLs must be https on port 443 and must not resolve to a private or internal address. A rejected URL answers 422 webhook_url_rejected.

Read this page as Markdown — plain text for AI agents. Every guide at once: llms-full.txt.

On this page