Agent skill

Autumn Webhooks Skill

Receive and verify Autumn webhooks (pricing and billing for AI apps). Use when setting up Autumn webhook handlers, debugging Svix signature verification (svix-id, svix-timestamp, svix-signature, whsec_ secret), handling retries that fail the 5-minute timestamp window, or handling events like billing.updated, balances.limit_reached, balances.usage_alert_triggered, billing.auto_topup_failed and invoice.finalized.

Install this skill

npx skills add hookdeck/webhook-skills --skill autumn-webhooks


Autumn handles pricing and billing for AI apps. Webhooks are optional, since Autumn keeps subscription state, usage and access in sync itself, but they let you trigger your own systems: activation emails, provisioning, deprovisioning on cancel, usage alerts, ledger syncs. Autumn delivers them through Svix.

When to Use This Skill

  • How do I receive Autumn webhooks?
  • How do I verify an Autumn webhook signature (svix-signature)?
  • How do I handle billing.updated, balances.limit_reached or billing.auto_topup_failed?
  • Why is my Autumn webhook signature verification failing?
  • Why do retried or replayed Autumn webhooks fail with "Message timestamp too old"?
  • Where do I get the Autumn webhook signing secret?

Verification (core)

HeaderValue
svix-idUnique message ID, the same on every retry of that message to that endpoint
svix-timestampUnix seconds when the message was sent
svix-signatureSpace-delimited list of v1,<base64> signatures

Signed content is {svix-id}.{svix-timestamp}.{raw body}, HMAC-SHA256, base64. The key is the base64-decoded part of the secret after whsec_. Autumn's docs point to the official svix library (npm and PyPI):

import { Webhook } from 'svix'; // svix 2.x is ESM-only

// rawBody: the exact bytes received (express.raw({ type: '*/*' }) / request.text()), NOT re-serialised JSON
function verifyAutumnWebhook(rawBody, headers) {
  const secret = process.env.AUTUMN_WEBHOOK_SECRET; // whsec_...
  if (!secret) throw new Error('AUTUMN_WEBHOOK_SECRET not set'); // fail closed → 500
  new Webhook(secret).verify(rawBody, {
    'svix-id': headers['svix-id'],
    'svix-timestamp': headers['svix-timestamp'],
    'svix-signature': headers['svix-signature'],
  }); // throws WebhookVerificationError → 400
  return JSON.parse(rawBody.toString('utf8')); // svix 2.x verify() returns nothing
}

Python: from svix.webhooks import Webhook, then Webhook(secret).verify(raw_body, headers) followed by json.loads(raw_body).

For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.

The 5-Minute Timestamp Window and Hookdeck Retries

The svix and standardwebhooks libraries always enforce a fixed 5-minute window on svix-timestamp, and it can't be turned off or widened. Retries and replays through Hookdeck (and any other queue or replay tool) resend the original request with the original svix-timestamp, and Svix's signature isn't regenerated. A delivery retried or replayed more than 5 minutes after Autumn sent it is rejected by the library as "Message timestamp too old", even though the signature is valid.

If you receive Autumn webhooks through Hookdeck, or want to replay old events, use the manual verifier in references/verification.md (HMAC-SHA256 over {id}.{timestamp}.{raw body}, key = base64-decoded secret after whsec_, constant-time comparison of every space-separated v1,<base64> entry). It lets you drop the window or widen it. The examples switch to it when AUTUMN_TIMESTAMP_TOLERANCE_SECONDS is set (off disables the window). Because the signature is then the only replay protection, make the handler idempotent (below).

The Signing Secret Is Shown Once

The API returns the whsec_ secret only when a webhook is created, by webhooks.create or webhooks.sync. webhooks.get never returns it, so store it immediately. Lost it? Create a new webhook. Autumn's docs don't say where the dashboard shows the secret for an endpoint added there. Details in references/setup.md.

Payload and Event Types

Autumn's example bodies are { "type": "<event>", "data": { ... } }. Dispatch on type. Its reference pages for invoice.finalized and vercel.* list fields without showing the envelope. Auto top-up events also carry a top-level id and occurred_at. Timestamps inside payloads are milliseconds since the Unix epoch.

EventFired when
billing.updatedA customer's plans change; data.plan_changes[] has action: activated, scheduled, updated or expired; data.tags may hold trial_ended or phase_changed
balances.limit_reachedA feature limit is hit; data.limit_type: included, max_purchase, spend_limit or usage_limit
balances.usage_alert_triggeredA configured usage alert threshold is crossed (data.usage_alert)
billing.auto_topup_succeededAn auto top-up granted prepaid balance
billing.auto_topup_failedAn auto top-up was blocked, declined or failed; branch on data.reason
invoice.finalizedA Stripe invoice is finalized and its line items reconciled

The API also accepts customer.products.updated and customer.threshold_reached, which have no reference page. Autumn's older docs give customer.products.updated its own shape (data.scenario, data.customer, data.updated_product, no plan_changes), and its changelog shows it firing alongside billing.updated, so don't handle the two alike. Unknown types should get a 200. See references/overview.md for payload details.

Delivery Semantics

  • Delivery is at least once and retried with exponential backoff when your endpoint errors or is unreachable. Retries use the same svix-id, so dedupe on it.
  • Return a 2xx quickly and process in the background.
  • Delivery attempts can be inspected and retried from the Autumn dashboard.

Environment Variables

AUTUMN_WEBHOOK_SECRET=whsec_xxxxx              # Returned once by webhooks.create / sync
AUTUMN_TIMESTAMP_TOLERANCE_SECONDS=            # Optional: seconds, or "off". Unset = svix library (fixed 5 minutes)

Local Development

# Express / Next.js (use 8000 for FastAPI). No install or account required.
npx hookdeck-cli listen 3000 autumn --path /webhooks/autumn

Add the printed URL as an endpoint in the Autumn dashboard (Developer → Webhooks → Add Endpoint). Autumn requires an https URL and rejects localhost, so use the tunnel URL.

Reference Materials


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Nov 5, 2026

View on GitHub →