Skip to main content
Every webhook delivery includes a cryptographic signature so you can verify the request came from your Exo app, not from a third party.

How signing works

When Exo sends a webhook, it:
  1. Serializes the JSON payload
  2. Computes an HMAC-SHA256 hash using the subscription’s secret as the key
  3. Sends the hash in the X-Exo-Signature header, prefixed with sha256=
The request also includes a User-Agent: Exo-Webhook/1.0 header.

Verifying in your receiver

To verify a webhook delivery, compute the same HMAC hash on your end and compare it to the signature header.
$payload = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_EXO_SIGNATURE'] ?? '';
$secret = 'your-webhook-secret';

$expected = 'sha256=' . bin2hex(
    hash_hmac('sha256', $payload, $secret, binary: true)
);

if (! hash_equals($expected, $signature)) {
    http_response_code(401);
    exit('Invalid signature');
}

$data = json_decode($payload, true);
// Process the webhook...

Important notes

  • Always use a constant-time comparison function (like hash_equals in PHP or hmac.compare_digest in Python) to prevent timing attacks.
  • The signature is computed on the raw JSON body of the request, not on any parsed or reformatted version. Make sure you read the raw body before parsing.
  • Each webhook subscription has its own unique secret, generated when the subscription is created. If you have multiple subscriptions, use the correct secret for each one.

Webhook request format

Every webhook delivery is an HTTP POST with these headers:
HeaderValue
Content-Typeapplication/json
X-Exo-Signaturesha256= followed by the hex-encoded HMAC hash
User-AgentExo-Webhook/1.0
The request body is a JSON object:
{
  "event": "on_create",
  "resource": "order",
  "data": {
    "id": 42,
    "order_number": "ORD-001",
    "status": "pending"
  },
  "timestamp": "2026-03-28T14:30:00.123456Z"
}

Handling failures

If your webhook receiver returns a non-2xx status code or the request times out (30 seconds), the delivery job throws an exception. Laravel’s queue system handles retries based on your queue configuration.
Configure your queue’s tries and backoff settings to control how many times failed webhook deliveries are retried.