🛡️Gatekeeper/ SDKs

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.

  1. Register an endpoint with webhooks.registerEndpoint(tenantId, url, events, secret). The URL must be public https.

  2. Dispatch an event with webhooks.dispatch(tenantId, eventType, payload). It returns one delivery record per matching endpoint.

  3. List delivery attempts for an endpoint with webhooks.deliveries(endpointId) to see status and attempt counts.

  4. On the receiver, recompute the HMAC over ${timestamp}.${rawBody} and compare it against the X-Webhook-Signature header 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);
}