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
- Open your workflow app and go to Webhooks in the sidebar.
- On the Outgoing tab, click Add endpoint.
- Enter the destination URL, select the events to subscribe to, and save.
- 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.