Payload Format

This page shows detailed payload examples and explains how to verify webhook signatures for security.

Signature Verification

Every webhook request includes a signature in the X-Otesse-Signature header. Always verify this signature to ensure the request came from Otesse and was not tampered with.

How to Verify

The signature is an HMAC-SHA256 hash of the request body using your webhook secret:

const crypto = require('crypto');

function verifyWebhookSignature(payload, signature, secret) {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(payload, 'utf8')
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}

// In your webhook handler:
app.post('/webhooks/otesse', (req, res) => {
  const signature = req.headers['x-otesse-signature'];
  const isValid = verifyWebhookSignature(
    JSON.stringify(req.body),
    signature,
    process.env.OTESSE_WEBHOOK_SECRET
  );

  if (!isValid) {
    return res.status(401).send('Invalid signature');
  }

  // Process the event...
  res.status(200).send('OK');
});

Python Example

import hmac
import hashlib

def verify_signature(payload: bytes, signature: str, secret: str) -> bool:
    expected = hmac.new(
        secret.encode('utf-8'),
        payload,
        hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(signature, expected)

Payload Examples

booking.created

{
  "id": "evt_01HQ3K5M7N8P9R",
  "type": "booking.created",
  "created_at": "2026-02-26T14:00:00Z",
  "data": {
    "id": "bk_01HQ3K5M7N8P9R",
    "status": "confirmed",
    "customer_id": "cust_01HQ3K5M7N",
    "industry": "cleaning",
    "scheduled_at": "2026-03-01T10:00:00Z",
    "address": {
      "street": "123 Main St",
      "city": "Eugene",
      "state": "OR",
      "zip": "97401"
    },
    "pricing": {
      "subtotal": 15000,
      "tax": 1275,
      "total": 16275
    }
  },
  "previous_data": null
}

payment.succeeded

{
  "id": "evt_02JR4L6N8O9Q0S",
  "type": "payment.succeeded",
  "created_at": "2026-02-26T18:05:00Z",
  "data": {
    "id": "pay_01HQ3K5M7N",
    "invoice_id": "inv_01HQ3K5M7N",
    "customer_id": "cust_01HQ3K5M7N",
    "amount": 16275,
    "currency": "usd",
    "payment_method": {
      "type": "card",
      "brand": "visa",
      "last4": "4242"
    },
    "stripe_charge_id": "ch_3abc123..."
  },
  "previous_data": null
}

Headers

Every webhook request includes these headers:

HeaderDescription
Content-Typeapplication/json
X-Otesse-SignatureHMAC-SHA256 signature
X-Otesse-EventEvent type (e.g., booking.created)
X-Otesse-DeliveryUnique delivery ID for idempotency
X-Otesse-TimestampUnix timestamp of when the event was sent
User-AgentOtesse-Webhooks/1.0

Idempotency

Use the X-Otesse-Delivery header to detect duplicate deliveries. Store processed delivery IDs and skip any that you have already handled. This prevents duplicate processing if Otesse retries a delivery.