Outbound Webhooks

Outbound webhooks notify external services when events happen in your workflow app. When a record is created, updated, or deleted, WorkApps delivers a signed HTTP POST to your configured endpoint URL.

Setup

  1. Open your workflow app and go to Webhooks in the sidebar.
  2. On the Outgoing tab, click Add endpoint.
  3. Enter the destination URL, select the events to subscribe to, and save.
  4. Copy the signing secret — you'll use it to verify incoming requests.

Events

Event When it fires
record.created A single record is created
record.updated A record is updated
record.deleted A record is deleted
record.bulk_created Records are created via a bulk operation
schema.updated The entity schema is modified (field added, removed, or changed)

Each endpoint can subscribe to any combination of events.

Delivery

Requests are delivered asynchronously via a background queue. WorkApps retries failed deliveries up to 5 times with exponential backoff:

Attempt Delay after previous failure
1 Immediate
2 10 seconds
3 60 seconds
4 5 minutes
5 30 minutes
(final) 2 hours

A delivery is considered successful if the endpoint returns any 2xx status code within 10 seconds. Redirects are not followed. If all retries are exhausted the delivery is marked failed and will not be retried again — check the delivery logs to replay manually.

Request Format

WorkApps sends a POST request with the following headers:

1Content-Type: application/json2X-WorkApps-Webhook-Id: <delivery-ulid>3X-WorkApps-Signature-256: sha256=<hmac-hex>4User-Agent: WorkApps-Webhook/1.0

The body is a JSON envelope:

1{2  "id": "01JQDELIVERY0000000000000",3  "event": "record.created",4  "app_id": "01JQAPP00000000000000000",5  "entity_key": "tasks",6  "timestamp": "2026-03-22T01:31:46+00:00",7  "data": { ... }8}

data by event type

record.created

1{2  "id": "01JQRECORD00000000000000",3  "title": "Fix login bug",4  "status": "open",5  "created_at": "2026-03-22T01:31:46+00:00",6  "updated_at": "2026-03-22T01:31:46+00:00"7}

record.updated

1{2  "id": "01JQRECORD00000000000000",3  "title": "Fix login bug",4  "status": "in_progress",5  "changed_fields": ["status"],6  "created_at": "2026-03-22T01:31:46+00:00",7  "updated_at": "2026-03-22T01:35:00+00:00"8}

changed_fields lists the field keys that were modified in this update.

record.deleted

1{2  "id": "01JQRECORD00000000000000",3  "title": "Fix login bug",4  "status": "done"5}

The last known field values are included so you can reference what was deleted.

record.bulk_created

1{2  "records": [3    {4      "id": "01JQRECORD00000000000001",5      "title": "Task A",6      "created_at": "2026-03-22T01:31:46+00:00",7      "updated_at": "2026-03-22T01:31:46+00:00"8    },9    {10      "id": "01JQRECORD00000000000002",11      "title": "Task B",12      "created_at": "2026-03-22T01:31:46+00:00",13      "updated_at": "2026-03-22T01:31:46+00:00"14    }15  ]16}

schema.updated

1{2  "change_type": "field_added"3}

Verifying Signatures

Every delivery includes an X-WorkApps-Signature-256 header containing an HMAC-SHA256 signature of the raw request body, signed with your endpoint's secret. Always verify this before processing the payload.

1import { createHmac, timingSafeEqual } from 'crypto';23function verifySignature(body, secret, signatureHeader) {4    const expected = 'sha256=' + createHmac('sha256', secret)5        .update(body)6        .digest('hex');78    const a = Buffer.from(signatureHeader);9    const b = Buffer.from(expected);1011    if (a.length !== b.length) return false;12    return timingSafeEqual(a, b);13}1415// Express example16app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {17    const sig = req.headers['x-workapps-signature-256'];18    if (!verifySignature(req.body, process.env.WEBHOOK_SECRET, sig)) {19        return res.status(401).send('Invalid signature');20    }21    // process payload...22    res.sendStatus(200);23});

Important: Compute the signature against the raw request body bytes, not a parsed JSON object. Use a constant-time comparison (timingSafeEqual) to prevent timing attacks.

Idempotency

The X-WorkApps-Webhook-Id header contains the delivery ULID. Store this value and check for duplicates before processing — network failures can cause the same delivery to arrive more than once even after a successful response was sent.

Loop Prevention

If your app uses both outbound and inbound webhooks, WorkApps automatically prevents infinite loops. When an outbound webhook is delivered, the request includes the X-WorkApps-Webhook-Id header. If that request reaches your inbound webhook URL and creates a record, WorkApps detects the header and does not fire record.created again.

If you are building your own relay (outbound → your server → inbound), forward the X-WorkApps-Webhook-Id header to trigger the same protection.

Delivery Logs

The Webhooks page in the dashboard shows a delivery log for each endpoint. For each delivery you can see the payload sent, the response status and body, the number of attempts, and whether it succeeded or failed. Failed deliveries can be replayed manually from the log.

Rotating the Signing Secret

Go to the webhook detail view and click Rotate secret. The new secret takes effect immediately for all subsequent deliveries. Update your receiving service before rotating to avoid a gap in verification.