Webhook Configuration
Outbound webhooks allow external systems to receive real-time notifications when events occur in Otesse. When a customer is created, an invoice is paid, or a job status changes, the system can automatically deliver a signed HTTP payload to your specified URL. This page covers creating webhook subscriptions, signature verification, delivery mechanics, and retry behavior.
Creating a Webhook Subscription
Step 1: Navigate to Webhooks
Go to Settings > Integrations > Webhooks and click "Create Webhook."
Step 2: Configure the Subscription
| Field | Description | Required |
|---|---|---|
| Name | A descriptive name (e.g., "CRM Sync - Customer Events") | Yes |
| Target URL | The HTTPS endpoint where payloads are delivered | Yes |
| HTTP Method | POST (default) or PUT | Yes |
| Event Types | Which events trigger delivery (select from grouped checklist) | Yes (at least one) |
| Custom Headers | Additional headers included with each delivery (max 10) | No |
Step 3: Receive Signing Secret
After creation, the system generates a cryptographic signing secret and displays it once. Copy and store this secret securely — it is used to verify that incoming webhooks are genuinely from Otesse and have not been tampered with.
The secret is never shown again. If you lose it, you must create a new webhook subscription.
Step 4: Test Delivery
The system automatically sends a test webhook after creation. The test payload includes:
{
"id": "delivery-uuid",
"type": "webhook.test",
"timestamp": "2026-03-01T12:00:00.000Z",
"data": {
"subscriptionId": "sub-uuid",
"timestamp": "2026-03-01T12:00:00.000Z"
},
"test": true
}
A green checkmark confirms successful delivery (2xx response). A red alert indicates failure with the response status code.
Signature Verification
All webhook deliveries are signed using HMAC-SHA256. Your receiving endpoint should verify the signature to ensure authenticity and integrity.
Signature Headers
Every delivery includes:
| Header | Description |
|---|---|
X-Webhook-Signature | sha256={hex signature} |
X-Webhook-Timestamp | Unix timestamp (seconds) |
X-Webhook-Id | Unique delivery ID (for idempotency) |
Verification Steps
- Extract the timestamp from
X-Webhook-Timestamp - Reject if the timestamp is older than 5 minutes (replay protection)
- Construct the signed payload:
{timestamp}.{raw request body} - Compute HMAC-SHA256 using your stored signing secret
- Compare the result to the signature in
X-Webhook-Signatureusing constant-time comparison
Example Verification (Node.js)
const crypto = require('crypto');
function verifyWebhook(payload, timestamp, signature, secret) {
// Reject old timestamps (5 minute window)
const age = Math.floor(Date.now() / 1000) - parseInt(timestamp);
if (age > 300) throw new Error('Timestamp too old');
// Compute expected signature
const signed = `${timestamp}.${payload}`;
const expected = crypto
.createHmac('sha256', secret)
.update(signed)
.digest('hex');
// Constant-time comparison
const sig = signature.replace('sha256=', '');
if (!crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) {
throw new Error('Invalid signature');
}
return true;
}
Available Event Types
Events follow the pattern {entity}.{action}:
Standard Events
| Entity | .created | .updated | .deleted |
|---|---|---|---|
| customer | Customer created | Customer updated | Customer deleted |
| invoice | Invoice created | Invoice updated | Invoice voided |
| payment | Payment received | Payment adjusted | Payment reversed |
| job | Job created | Job status changed | Job cancelled |
| employee | Employee added | Employee updated | Employee deactivated |
| product | Product created | Product updated | Product discontinued |
| appointment | Scheduled | Rescheduled | Cancelled |
Integration Events
| Event | Trigger |
|---|---|
integration.connected | New integration connected |
integration.disconnected | Integration disconnected |
sync.completed | Sync job completed |
sync.failed | Sync job failed |
Payload Structure
All payloads follow a consistent format:
{
"id": "delivery-uuid",
"type": "customer.created",
"timestamp": "2026-03-01T12:00:00.000Z",
"orgId": "org-uuid",
"data": { "id": "entity-uuid", "...": "entity fields" },
"metadata": {
"actorId": "user-uuid",
"actorType": "user",
"source": "dashboard"
}
}
Retry Policy
Failed deliveries are retried with exponential backoff:
| Attempt | Delay | Total Wait |
|---|---|---|
| 1 | 10 seconds | 10s |
| 2 | 60 seconds | ~1m |
| 3 | 10 minutes | ~11m |
| 4 | 1 hour | ~1h 11m |
| 5 (final) | N/A | — |
After all 5 attempts fail, the delivery status is set to "dead." If a subscription accumulates 10 or more consecutive dead deliveries, it is automatically disabled and the administrator is notified.
Managing Subscriptions
| Action | Description |
|---|---|
| Edit | Change name, target URL, HTTP method, event types, or custom headers |
| Enable/Disable | Toggle delivery without deleting configuration |
| Delete | Soft-delete the subscription (delivery history preserved for audit) |
| View History | See all delivery attempts with status, response codes, and payloads |
On this page