Receive signed vendor events
Receive configured operational events without turning an event notification into a second source of entitlement truth.
Arrange endpoint configuration first
Outbound vendor events require a configured endpoint, subscribed event types and an agreed signing secret. This public reference does not define a self-service webhook registration API. Confirm enablement with BEXO before relying on event delivery.
The committed dispatcher emits verification.allowed and verification.denied for normal POS results. Event availability is configuration-dependent. Do not assume that suggested event names, such as membership.activated, are emitted merely because an integration plan mentions them.
Headers and event envelope
| Header | Meaning |
|---|---|
| Content-Type: application/json | JSON event body. |
| X-Bexo-Event-Id | Event ID; must match the signed body id. |
| X-Bexo-Event-Type | Event type; must match the signed body type. |
| X-Bexo-Signature | sha256= followed by the HMAC-SHA256 hexadecimal digest of the raw request body. |
{
"id": "11111111-1111-4111-8111-111111111111",
"type": "verification.denied",
"occurredAt": "2026-10-19T09:00:00Z",
"data": {
"tenantId": "22222222-2222-4222-8222-222222222222",
"terminalId": "33333333-3333-4333-8333-333333333333",
"terminalLabel": "Reception",
"verificationLogId": "44444444-4444-4444-8444-444444444444",
"decision": "deny",
"reason": "member_not_found",
"occurredAt": "2026-10-19T09:00:00Z"
}
}This is a synthetic example. Regular verification event data contains operational references and a result; do not expect a complete member profile or a raw card identifier in the event.
Verify exact request bytes
Capture the raw bytes before JSON parsing. Do not reserialize a parsed object for HMAC verification: whitespace and byte ordering matter. Use constant-time comparison and reject malformed signatures before processing the payload.
import { createHmac, timingSafeEqual } from 'node:crypto';
export function verifyBexoEvent(rawBody, signature, secret) {
if (!Buffer.isBuffer(rawBody)) throw new Error('Raw bytes required');
if (!/^sha256=[a-f0-9]{64}$/.test(signature || '')) return false;
const expected = createHmac('sha256', secret).update(rawBody).digest();
const received = Buffer.from(signature.slice(7), 'hex');
return expected.length === received.length &&
timingSafeEqual(expected, received);
}Load the secret from your server’s secret store. Do not put it in client code or return it from a diagnostics endpoint. After verifying, parse the signed body and validate the event/tenant against your configured integration.
Acknowledge, deduplicate and reconcile
- Reject an invalid signature or an unexpected tenant.
- Check that header IDs/types match the signed body.
- Durably record the event ID and enqueue permitted work before returning a 2xx response.
- Treat an event already durably accepted as a duplicate and acknowledge it without repeating side effects.
- Reconcile access/payment facts through the supported API when needed. Do not grant membership from a free-form event name.
The dispatcher uses a 10-second request timeout. Failed attempts record delivery status and retry metadata; this reference does not promise a completed automatic retry schedule, delivery order or delivery SLA. Coordinate recovery/replay with BEXO. A valid signature does not itself prevent replay—event-ID deduplication is required.