Webhooks push tenant events to your URL with an HMAC signature so you can trust the payload. This guide registers an endpoint with a shared secret, dispatches an event, and lists delivery attempts. Outbound URLs are SSRF-guarded: only public https hosts are accepted. The receiver verifies each request by recomputing the signature over timestamp.body.
-
Register an endpoint with
webhooks.registerEndpoint(tenantId, url, events, secret). The URL must be publichttps. -
Dispatch an event with
webhooks.dispatch(tenantId, eventType, payload). It returns one delivery record per matching endpoint. -
List delivery attempts for an endpoint with
webhooks.deliveries(endpointId)to see status and attempt counts. -
On the receiver, recompute the HMAC over
${timestamp}.${rawBody}and compare it against theX-Webhook-Signatureheader before trusting the payload.
import { GatekeeperCore, WebhooksService } from '@orkait/sdk';
const core = new GatekeeperCore({ baseUrl: 'https://gatekeeper-api.example.workers.dev' });
core.setToken(accessToken);
const webhooks = new WebhooksService(core);
// 1. Register a signed endpoint. Public https only.
const endpoint = await webhooks.registerEndpoint(
't_acme',
'https://hooks.acme.com/gatekeeper',
['usage.recorded', 'key.revoked'],
's3cret-shared-with-receiver',
);
// 2. Dispatch an event - one delivery per matching endpoint.
const deliveries = await webhooks.dispatch('t_acme', 'usage.recorded', {
tenantId: 't_acme',
amount: 1,
});
console.log(deliveries.map((d) => `${d.eventType}: ${d.status}`));
// 3. List delivery attempts for the endpoint.
const history = await webhooks.deliveries(endpoint.id, { limit: 20 });
for (const d of history.items) {
console.log(`${d.id} ${d.status} attempts=${d.attemptCount}`);
}Receiver-side verification (the signature covers timestamp.body):
import { createHmac, timingSafeEqual } from 'node:crypto';
function verify(secret: string, rawBody: string, headers: Record<string, string>): boolean {
const timestamp = headers['x-webhook-timestamp'];
const received = headers['x-webhook-signature']; // 'sha256=<hex>'
const expected = 'sha256=' + createHmac('sha256', secret).update(`${timestamp}.${rawBody}`).digest('hex');
const a = Buffer.from(received);
const b = Buffer.from(expected);
return a.length === b.length && timingSafeEqual(a, b);
}