Developer portal
Webhooks
Push events to your systems as they happen in QEHS. Every delivery is signed with HMAC-SHA-256 and retried with exponential backoff for 24 hours.
Event catalog
| Event | Description | Payload shape |
|---|---|---|
record.created | A new record is committed to a module. | { id, moduleKey, createdAt, createdBy, payload } |
record.updated | Any field or workflow state changes on a record. | { id, moduleKey, changes[], updatedAt, updatedBy } |
record.transitioned | A workflow transition fires (including state name before/after). | { id, moduleKey, from, to, actor, at } |
record.deleted | A record is soft-deleted. | { id, moduleKey, deletedAt, deletedBy } |
module.published | A Composer version is published to production. | { moduleKey, version, publishedBy, publishedAt } |
user.provisioned | SCIM or self-sign-up created a new user. | { userId, email, provisionedAt, source } |
user.deprovisioned | A user is disabled or removed via SCIM. | { userId, at, source } |
attachment.uploaded | An attachment is completed and virus-scanned. | { attachmentId, recordId, sizeBytes, mimeType } |
export.completed | An asynchronous export job has a downloadable artefact. | { jobId, downloadUrl, expiresAt, kind } |
billing.subscription.updated | Plan, seat count, or trial state changed (tenant-owner scope). | { tenantId, plan, seats, status, effectiveAt } |
Signature verification
Every outbound delivery includes an X-QEHS-Signature header of the form t=<timestamp>,v1=<hex-hmac>. Re-compute the HMAC-SHA-256 of <timestamp>.<raw-body> using your endpoint's shared secret and compare with a constant-time equality check. Reject anything older than 5 minutes.
// Node.js / TypeScript
import crypto from 'node:crypto';
export function verify(body: string, header: string, secret: string): boolean {
const [ts, sig] = header.split(',').map((p) => p.split('=')[1]);
const expected = crypto
.createHmac('sha256', secret)
.update(`${ts}.${body}`)
.digest('hex');
const match = crypto.timingSafeEqual(Buffer.from(sig, 'hex'), Buffer.from(expected, 'hex'));
const fresh = Math.abs(Date.now()/1000 - Number(ts)) < 300;
return match && fresh;
}Delivery semantics
- At-least-once delivery. Use the
X-QEHS-Delivery-Idheader to dedupe. - 8 retry attempts over ~24 hours with exponential backoff (1m, 5m, 15m, 1h, 4h, 10h, 20h).
- Any non-2xx response (or timeout > 15s) triggers a retry.
- A delivery failing all retries moves to the dead-letter queue; admins receive an email.
- Replay individual deliveries from
https://app.qehsethos.com/settings/webhooks.
Setup
- Open
https://app.qehsethos.com/settings/webhooksand click "Add endpoint". - Enter the URL, pick events, optionally filter by module key.
- Copy the shared secret once — you will need it to verify signatures.
- Click "Send test" to trigger a sample delivery to your endpoint.