Webhooks

Receive real-time notifications when events happen in your Expereon account.

Overview

Instead of polling the API for changes, configure webhook endpoints to receive HTTP POST requests whenever invoices, payments, or other resources change state.

Setting up a webhook

Create a webhook endpoint via the API or the Expereon dashboard:

curl -X POST "https://api.expereon.org/api/v1/settings/webhooks" \
  -H "X-API-Key: exp_prod_xxxxxxxxxxxx" \
  -H "X-API-Secret: your-secret-key" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-app.com/webhooks/expereon",
    "eventTypes": ["BOOKING_CREATED", "BOOKING_UPDATED", "PAYMENT_COMPLETED"],
    "authMethod": "NONE",
    "signingSecret": "whsec_your_secret_here"
  }'

authMethod is one of NONE, BASIC_AUTH, or API_KEY. BASIC_AUTH and API_KEY additionally require an auth header name and value. The optional signingSecret field is independent of authMethod and can be set alongside any of the three to enable payload signature verification.

Requirements

Sandbox webhooks

You can register a webhook using a sandbox API key against the same api.expereon.org host the same way you would in production - sandbox is a key-prefix on the same host, not a separate host.

Current limitation: no per-environment isolation Webhook endpoints are configured per key/URL, not scoped to an environment field - this is not yet modeled. The sandbox reset endpoint (POST /api/v1/settings/api-keys/sandbox/reset) does not remove your webhook registrations; it only clears sandbox API keys, sandbox logs, sandbox invoices, and sandbox bookings.

Events

EventDescription
BOOKING_CREATEDA new booking was created
BOOKING_UPDATEDA booking was updated
PAYMENT_COMPLETEDA payment was successfully processed
DISPUTE_INITIATEDBuyer raised a dispute on the invoice

Payload format

Every webhook delivery is a JSON POST request with a flat body keyed by event (not a nested envelope). The event name is also sent in the X-Expereon-Event header.

{
  "event": "BOOKING_CREATED",
  "bookingId": "bk_xyz789",
  "bookingReference": "BK-2024-001",
  "status": "CONFIRMED",
  "totalPrice": "1500.00",
  "currency": "USD"
}

Signature verification

Every webhook request includes an X-Webhook-Signature header with the value sha256=<base64-signature>, an HMAC-SHA256 signature of the request body, base64-encoded, using your signing secret.

Verifying signatures

// Node.js example
const crypto = require('crypto');

function verifyWebhook(body, signatureHeader, secret) {
  const signature = signatureHeader.replace(/^sha256=/, '');
  const expected = crypto
    .createHmac('sha256', secret)
    .update(body, 'utf8')
    .digest('base64');
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}

// In your webhook handler:
app.post('/webhooks/expereon', (req, res) => {
  const signature = req.headers['x-webhook-signature'];
  const isValid = verifyWebhook(req.rawBody, signature, 'whsec_your_secret');

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

  const event = req.body;
  switch (event.event) {
    case 'BOOKING_CREATED':
      // Handle new booking
      break;
    case 'PAYMENT_COMPLETED':
      // Handle completed payment
      break;
  }

  res.status(200).send('OK');
});
Always verify signatures Never process webhook payloads without verifying the signature first. This prevents attackers from sending fake events to your endpoint.

Retry policy

If your endpoint fails to respond with 2xx, Expereon retries with geometric backoff: 5 minutes * 2^n (5, 10, 20, 40 minutes, ...), bounded by a 24-hour maximum delivery lifetime. After 24 hours, the delivery is marked as failed.

Testing webhooks

Use the test endpoint to send a sample event to your webhook:

curl -X POST "https://api.expereon.org/api/v1/settings/webhooks/{webhookId}/test" \
  -H "X-API-Key: exp_prod_xxxxxxxxxxxx" \
  -H "X-API-Secret: your-secret-key"

This sends {"event": "test", "message": "Webhook test from Expereon"} to your endpoint. Use this to verify your handler and signature validation work correctly.

Best practices