Outbound webhooks notify external services when events happen in your workflow app.
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 |
Delivery
Requests are delivered asynchronously. HASP 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.
Content-Type: application/json
X-Hasp-Webhook-Id: <delivery-ulid>
X-Hasp-Signature-256: sha256=<hmac-hex>
User-Agent: Hasp-Webhook/1.0
Body envelope:
{
"id": "01JQDELIVERY0000000000000",
"event": "record.created",
"app_id": "01JQAPP00000000000000000",
"entity_key": "tasks",
"timestamp": "2026-03-22T01:31:46+00:00",
"data": { ... }
}
Verifying Signatures
Every delivery includes an X-Hasp-Signature-256 header with an HMAC-SHA256 signature signed with your endpoint’s secret.
import { createHmac, timingSafeEqual } from 'crypto';
function verifySignature(body, secret, signatureHeader) {
const expected = 'sha256=' + createHmac('sha256', secret)
.update(body)
.digest('hex');
const a = Buffer.from(signatureHeader);
const b = Buffer.from(expected);
if (a.length !== b.length) return false;
return timingSafeEqual(a, b);
}
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const sig = req.headers['x-hasp-signature-256'];
if (!verifySignature(req.body, process.env.WEBHOOK_SECRET, sig)) {
return res.status(401).send('Invalid signature');
}
res.sendStatus(200);
});
Compute the signature against the raw request body bytes, not a parsed JSON object. Use a constant-time comparison to prevent timing attacks.
Idempotency
The X-Hasp-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.
Rotating the Signing Secret
Go to the webhook detail view and click Rotate secret. The previous secret remains valid for 7 days after rotation.