Documentation

Everything you need to run webhooks in production.

Axel docs are split into four lanes: get started, primitives, operate, and reference. Each page is short by design — if a page can't be skimmed in 60 seconds, it's split into smaller ones.

Get started

Quickstart — accept your first webhook

CLI or curl. Under two minutes to a durable, searchable event.

1.

Create a source via the API

curl -X POST https://api.axelapp.ai/v1/sources \
  -H "Authorization: Bearer $AXEL_API_KEY" \
  -d '{ "name": "my-webhook", "max_events_per_minute": 600 }'

The response includes a per-source ingest token. Store it as a secret in your producer's environment.

2.

Point the producer at the ingest URL

POST https://ingest.axelapp.ai/in/{source_id}
  x-axel-token: <token>
  content-type: application/json

  { "type": "order.created", "id": "ord_123", ... }

The endpoint returns 202 in under 250ms p95. Your event is durable in R2 before the response.

3.

Watch it land in the dashboard

# Open https://app.axelapp.ai
# → Overview shows the event count tick up
# → Usage tab shows the source-level breakdown
# → Deliveries shows any failed attempts

Every event you accept is searchable in ClickHouse for 30 days, including the raw payload and headers.

Prefer the CLI? axel auth login, axel sources create, then axel listen or point your webhook producer at the ingest URL shown in the dashboard.

Sources

Custom / generic webhook sources

Any producer that can POST JSON (or form / bytes) is supported.

Create a source with curl or the dashboard, then POST to https://ingest.axelapp.ai/in/{source_id} with the x-axel-token header you received. No signature required.

curl -X POST https://ingest.axelapp.ai/in/src_01H... \
  -H "x-axel-token: axel_tok_..." \
  -H "content-type: application/json" \
  -d '{ "type": "order.created", "id": "ord_123", ... }'

You can also bring a custom HMAC secret and Axel will verify an X-Signature (or configurable header) using constant-time verification.

Primitives

Webhook sources

Sources are the entry points. Each source has its own token (or signing secret), rate limit, body size cap, and nesting depth guard.

  • Per-source rate limits (e.g. 2,500 events/min) protect downstream systems.
  • Body cap (256 KB default) and depth cap (24 levels) reject pathological payloads early.
  • Every accepted event is written to R2 before the 202 response is sent.
  • Token values are stored only as SHA-256 hashes; never in logs.

Routes: declarative filters & transforms

Routes decide where events go and what they look like. Rules are data, not code.

  • Filter by event type (e.g. payments.live matches invoice.paid etc.).
  • Declarative transforms: select/rename fields by JSON path, drop fields, pass the whole payload through, or wrap it as a JSONB column.
  • Same route engine runs at the edge ingest worker and in the Node router — no eval, no sandboxed user code to escape.
  • Fan-out to many destinations from one source with per-(route, destination) idempotency keys.

Destinations

Deliver exactly once (with retries) to the systems you already run.

  • Signed webhook: POST with HMAC + deterministic idempotency header so receivers can dedupe safely.
  • Postgres / MongoDB: insert into JSONB or native collections. Column projection supported.
  • S3 / Cloudflare R2: write JSON objects with templated keys (date, event type, id, etc.).
  • Databricks: drop files into Unity Catalog volumes for Auto Loader / Delta Live Tables.

Idempotency

Every delivery attempt uses a stable key of the form workspace:event:route:destination. Retries and replays reuse the same key so duplicate work is impossible even across queue shards.

Operate

Replays

Reproduce any event you ever received, exactly.

From the dashboard or CLI:

# Dashboard: click any event → Replay
# CLI (exact bytes to your laptop)
axel replay evt_01HZQ8R7XK --forward-to http://localhost:3000/webhook
  • Replays pull the original payload + headers from R2 (30-day retention).
  • Signature headers that would be stale are stripped for localhost forwards.
  • Replay creates a fresh trace so you can compare original vs. re-delivery.

Failed deliveries & the Inbox

Every delivery attempt is recorded. Permanent failures (after all retries) land in the Inbox with one-click Retry and Mute. No SQL, no digging through DLQ tables.

The trace view shows every hop: receipt, R2 persist, route match, each transform, each HTTP attempt + status + reason (TLS error, 503, timeout, schema guard, etc.).

Transform & route errors

If a declarative transform or filter rejects an event (e.g. depth > 24, required field missing after select), the event is marked and either dropped or dead-lettered according to your source settings. You see the exact evaluation in the trace.

Scaling knobs

  • Per-source rate limits + body/depth caps are your first line of defense.
  • Router and delivery workers use bounded concurrency and sharded queues (Cloudflare Queues + internal).
  • Raw payloads: 30 days in R2. ClickHouse traces: 30 days TTL. Dead letters kept longer (1 year).
  • Dashboard and API give you live p95 ingest latency and per-route delivery metrics.
Reference

Event envelope (what you receive back)

Every stored event carries the original payload plus Axel metadata. Example shape:

{
  "event_id": "evt_01HZQ8R7XK",
  "source": "my-webhook",
  "event": "order.created",
  "received_at": "2026-06-16T14:22:09.123Z",
  "headers": { "x-my-signature": "..." },
  "payload": { "id": "evt_...", "type": "order.created", ... },
  "routes": ["orders.live"]
}

Retry policy (defaults)

Up to 12 delivery attempts with exponential backoff. Retries are never billable. You can tune per-source in the dashboard.

ClickHouse observability

All receipt, routing, transform, and delivery rows are queryable in ClickHouse for 30 days (full raw payload + headers + failure reasons). The dashboard search and the axel CLI use these tables. Export or run your own queries from the usage / deliveries views.

Important status & error reasons

  • 202: accepted (written to R2, queued for routing)
  • 429: rate limited at source
  • 413 / depth errors: body or nesting exceeded caps
  • Signature / auth failures: rejected before durable store
  • Destination 4xx/5xx, timeouts, TLS errors: retried per policy, then DLQ

Need a question answered?

Docs are intentionally concise. If something is missing or unclear, email the founders — we read every message and improve the site from real questions.