aaune

Asynchronous delivery

Signed booking webhooks

Webhook delivery is driven by the transactional booking outbox. A booking remains confirmed even if notification delivery fails; delivery state is never booking truth.

Manage scope
aune.webhooks.manage
Signature
HMAC-SHA256 over id.timestamp.raw-body
Timeout
8 seconds
Redirects
Rejected
Secret
Shown once

Register an endpoint

POST `/v1/webhook-endpoints` with an OAuth token carrying `aune.webhooks.manage`. Destinations must use public HTTPS on port 443; Aune resolves DNS and rejects private, loopback, link-local, multicast, local, and internal targets.

Endpoint registration
curl -X POST "https://aune-homepage-chat.vercel.app/v1/webhook-endpoints" \
  -H "Authorization: Bearer $AUNE_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  --data '{"url":"https://agent.example/aune/webhooks","event_types":["booking.confirmed","booking.rescheduled","booking.cancelled"]}'

Verify before parsing

Read the exact raw bytes. Reject stale timestamps, compare signatures in constant time, and deduplicate `Webhook-Id`. Only then parse JSON or trigger side effects.

Node.js verification core
import { createHmac, timingSafeEqual } from "node:crypto";

const signed = `${webhookId}.${webhookTimestamp}.${rawBody}`;
const expected = `v1,${createHmac("sha256", secret)
  .update(signed)
  .digest("base64url")}`;

const valid = Math.abs(Date.now() / 1000 - Number(webhookTimestamp)) <= 300 &&
  timingSafeEqual(Buffer.from(signature), Buffer.from(expected));

Event contract

  • `booking.confirmed`: durable booking committed.
  • `booking.rescheduled`: same booking moved to a newly revalidated option.
  • `booking.cancelled`: booking lifecycle status changed to cancelled.
  • `booking.provider_action_required`: provider-side follow-up is required.
  • Replay through `POST /v1/webhook-events/{outbox_id}/replay`; ownership is bound to the OAuth client.