Receiving webhooks

Receive webhooks from outside services through a relay endpoint that verifies senders before anything reaches your app.

Your app can receive webhooks from outside services — a payment completed, a GitHub push.
Only the AxHub receive endpoint's address is exposed outside, and only verified requests reach your app.

How it works

outside service → receive endpoint (hooks.{company}… /r/{endpoint id}) → sender verification → your app's route
  • The endpoint address is separate from your app's address. Redeploy the app or change internal paths, and the address you handed out stays the same.
  • Only verified requests reach the app, with the original method, body, and query preserved.

What you need

  • A connected app — webhooks are received per app. If you don't have one yet, start with Build and deploy your app.
  • CLI login — endpoints are managed with the axhub relay commands.

Create a receive endpoint

axhub relay create --app <app> --name <endpoint-name> --verify-mode key

The output shows the receive address, the key, and the receive contract. That output is the authority.

The key is shown only this once. If you lose it, issue a new one with axhub relay rotate-key.

Pick the sender verification (--verify-mode) that matches the outside service.

verify-modeWhat the sender provides
key (default)The shared key in an X-AxHub-Key header
hmacHMAC-SHA256 of the raw body as X-Signature-256: sha256=<hex>
noneNo verification. Only for senders you trust

Receive it in your app

The receiving side is not the SDK — it's a plain route handler in your framework. In Next.js it looks like this.

app/api/hook/route.ts
import { createHmac, timingSafeEqual } from 'crypto';

export async function POST(req: Request): Promise<Response> {
  const secret = process.env.AXHUB_RELAY_SIGNING_SECRET ?? '';
  const rawBody = await req.text();
  const provided = req.headers.get('x-axhub-signature') ?? '';
  const expected = 'sha256=' + createHmac('sha256', secret).update(rawBody).digest('hex');
  const ok = secret !== '' && provided.length === expected.length &&
    timingSafeEqual(Buffer.from(provided), Buffer.from(expected));
  if (!ok) {
    return new Response('unauthorized', { status: 401 });
  }
  const deliveryId = req.headers.get('x-axhub-delivery') ?? '';
  // Retries can deliver the same event again — handle it once, keyed by deliveryId.
  console.log('inbound webhook', deliveryId, rawBody.length);
  // Return 2xx once handled; anything else gets redelivered.
  return new Response('ok', { status: 200 });
}
  • Every delivery carries an X-AxHub-Signature: sha256=<hex> signature. Recompute the HMAC-SHA256 of the raw body with the AXHUB_RELAY_SIGNING_SECRET environment variable, injected automatically at deploy time, and only requests that came through the receive endpoint get through. Nothing to configure.
  • Every delivery also carries an X-AxHub-Delivery header (a unique delivery id). Retries can bring the same delivery twice, so make handling idempotent on that value.
  • The key is used only between the sender and the receive endpoint. Once verification passes, the endpoint strips credential headers like X-AxHub-Key before forwarding, so don't check the key again in your app.

Delivery and retries

Delivery modeBehavior
durable (default)Redelivers with growing backoff until your app returns 2xx (1m → 5m → 30m → 2h → 6h, then marked failed)
syncPasses your app's response straight back to the sender

Request bodies default to 1MiB (413 beyond that). Intake defaults to 300 per minute, and going over returns 429.

Operating it

What you wantCommand
List endpointsaxhub relay list --app <app>
See deliveries (status, attempts, response code)axhub relay deliveries <endpoint id> --app <app>
Resend a failed deliveryaxhub relay replay <endpoint id> <delivery id> --app <app> --execute
Rotate the keyaxhub relay rotate-key <endpoint id> --app <app> --execute
Close an endpoint (irreversible)axhub relay delete <endpoint id> --app <app> --execute

You've succeeded when

Put the receive address and key into the outside service's webhook settings and send a test event.
If axhub relay deliveries records the delivery with a 2xx response code, you're done.