Developers
Webhooks
Outbound webhook delivery contract, headers, payload keys, and currently supported event types.
Overview
Webhook contract
Webhooks deliver real-time events when incidents, shifts, or other resources change. Default environment is sandbox.
| Field | Value |
|---|---|
| Source | webhook-fixture-dispatch-contract |
| Signature algorithm | hmac-sha256 |
| Sandbox endpoint | https://sandbox.example.com/webhooks/arcova |
| Production endpoint | https://api.example.com/webhooks/arcova |
| Sandbox secret prefix | whsec_sandbox_ |
| Production secret prefix | whsec_live_ |
Use production webhook secrets only after sandbox deliveries and signature verification checks pass.
Reference
Headers and payload
Required headers
Content-TypeX-Webhook-SignatureX-Webhook-TimestampX-Webhook-EventX-Webhook-IdX-Idempotency-Key
Required payload keys
eventtimestamptimestamp_isowebhook_ididempotency_keydata
Example payload (incident.created)
{
"id": "evt_abc123def456",
"type": "incident.created",
"created_at": 1700000000,
"data": {
"object": {
"id": "inc_xyz789",
"location_id": "loc_123",
"location_name": "Main Office Building",
"status": "open",
"priority": "high",
"reported_by": "John Smith",
"created_at": "2024-01-15T10:30:00Z",
"updated_at": "2024-01-15T10:30:00Z"
},
"previous_attributes": null
}
}Reference
Event types
| Event | Description |
|---|---|
webhook.test | Test event |
employee.created | Created event |
employee.updated | Updated event |
employee.terminated | Terminated event |
shift.created | Created event |
shift.updated | Updated event |
shift.cancelled | Cancelled event |
shift.assigned | Assigned event |
shift.unassigned | Unassigned event |
timeentry.clockin | Clockin event |
timeentry.clockout | Clockout event |
invoice.created | Created event |
invoice.sent | Sent event |
invoice.paid | Paid event |
invoice.overdue | Overdue event |
invoice.voided | Voided event |
payment.received | Received event |
payment.failed | Failed event |
payment.refunded | Refunded event |
operations.incident_created | Incident Created event |
operations.incident_updated | Incident Updated event |
operations.incident_resolved | Incident Resolved event |
operations.dispatch_created | Dispatch Created event |
operations.dispatch_assigned | Dispatch Assigned event |
operations.dispatch_completed | Dispatch Completed event |
compliance.credential_expiring | Credential Expiring event |
client.created | Created event |
client.updated | Updated event |
contract.created | Created event |
contract.renewed | Renewed event |
contract.expiring | Expiring event |
training.student_created | Student Created event |
training.student_updated | Student Updated event |
training.student_verified | Student Verified event |
training.enrollment_created | Enrollment Created event |
training.enrollment_confirmed | Enrollment Confirmed event |
training.enrollment_completed | Enrollment Completed event |
training.enrollment_cancelled | Enrollment Cancelled event |
training.enrollment_refunded | Enrollment Refunded event |
training.class_created | Class Created event |
training.class_updated | Class Updated event |
training.class_started | Class Started event |
training.class_completed | Class Completed event |
training.class_cancelled | Class Cancelled event |
training.class_rescheduled | Class Rescheduled event |
training.class_unpublished | Class Unpublished event |
training.class_deleted | Class Deleted event |
training.certificate_issued | Certificate Issued event |
training.certificate_expired | Certificate Expired event |
training.certificate_revoked | Certificate Revoked event |
training.attendance_checkin | Attendance Checkin event |
training.attendance_checkout | Attendance Checkout event |
training.attendance_missed | Attendance Missed event |
Behavior
Delivery and retries
Webhooks are retried automatically when your endpoint returns a non-2xx response or times out. Treat delivery as best-effort — design your handler to be idempotent.
Retry schedule
| Attempt | Delay |
|---|---|
| 1 | Immediate |
| 2 | 1 minute |
| 3 | 5 minutes |
| 4 | 15 minutes |
| 5 | 1 hour |
| 6 | 6 hours |
| 7 | 12 hours |
- Timeout: 30 seconds per attempt.
- Max attempts: 7 (gives up after ~24 hours).
- Idempotency: the
X-Idempotency-Keyheader lets you detect duplicates safely.
Security
Signature format
The X-Webhook-Signature header carries a timestamp and a signature in a structured format.
X-Webhook-Signature: t=1700000000,v1=a1b2c3d4e5f6...
Format: t=<unix-timestamp>,v1=<hex-signature>
Components:
- t: Unix timestamp when signature was created
- v1: HMAC-SHA256 signature of "timestamp.raw_body" using webhook secretVerification steps
- Extract
tandv1from the header. - Reject if the timestamp is older than 5 minutes (replay protection).
- Construct the signed payload:
timestamp + "." + raw_request_body. - Compute HMAC-SHA256 using your webhook secret.
- Compare the computed signature to
v1using constant-time comparison.
Patterns
Idempotency
Handle duplicate deliveries safely by storing theX-Idempotency-Keyyou have already processed for at least 24 hours.
// Example: deduplicate using X-Idempotency-Key
const idempotencyKey = headers['x-idempotency-key'];
if (await processedWebhooks.has(idempotencyKey)) {
// Already processed — return 200 to stop retries
return res.status(200).send('OK');
}
await processEvent(payload);
await processedWebhooks.add(idempotencyKey);
return res.status(200).send('OK');Examples
Verification snippets
Sandbox (https://sandbox.example.com/webhooks/arcova)
const timestamp = headers["X-Webhook-Timestamp"];
const signatureHeader = headers["X-Webhook-Signature"];
const signedPayload = timestamp + "." + rawBody;
const expected = crypto
.createHmac("sha256", "whsec_sandbox_your_secret")
.update(signedPayload)
.digest("hex");Production (https://api.example.com/webhooks/arcova) — intentional switch only
Use production webhook secrets only after sandbox deliveries and signature verification checks pass.
const timestamp = headers["X-Webhook-Timestamp"];
const signatureHeader = headers["X-Webhook-Signature"];
const signedPayload = timestamp + "." + rawBody;
const expected = crypto
.createHmac("sha256", "whsec_live_your_secret")
.update(signedPayload)
.digest("hex");