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

FieldDescriptionRequired
NameA descriptive name (e.g., "CRM Sync - Customer Events")Yes
Target URLThe HTTPS endpoint where payloads are deliveredYes
HTTP MethodPOST (default) or PUTYes
Event TypesWhich events trigger delivery (select from grouped checklist)Yes (at least one)
Custom HeadersAdditional 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:

HeaderDescription
X-Webhook-Signaturesha256={hex signature}
X-Webhook-TimestampUnix timestamp (seconds)
X-Webhook-IdUnique delivery ID (for idempotency)

Verification Steps

  1. Extract the timestamp from X-Webhook-Timestamp
  2. Reject if the timestamp is older than 5 minutes (replay protection)
  3. Construct the signed payload: {timestamp}.{raw request body}
  4. Compute HMAC-SHA256 using your stored signing secret
  5. Compare the result to the signature in X-Webhook-Signature using 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
customerCustomer createdCustomer updatedCustomer deleted
invoiceInvoice createdInvoice updatedInvoice voided
paymentPayment receivedPayment adjustedPayment reversed
jobJob createdJob status changedJob cancelled
employeeEmployee addedEmployee updatedEmployee deactivated
productProduct createdProduct updatedProduct discontinued
appointmentScheduledRescheduledCancelled

Integration Events

EventTrigger
integration.connectedNew integration connected
integration.disconnectedIntegration disconnected
sync.completedSync job completed
sync.failedSync 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:

AttemptDelayTotal Wait
110 seconds10s
260 seconds~1m
310 minutes~11m
41 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

ActionDescription
EditChange name, target URL, HTTP method, event types, or custom headers
Enable/DisableToggle delivery without deleting configuration
DeleteSoft-delete the subscription (delivery history preserved for audit)
View HistorySee all delivery attempts with status, response codes, and payloads