webhooks.xyz

API

Everything you can click is also an API call. Create a token on your account page, then send it as Authorization: Bearer whx_…. JSON in, JSON out. The machine-readable document is at /api/openapi.json.

The same API is also served on its own host, https://api.webhooks.xyz. Use it from scripts and browser apps: it has no pages or cookies, only /api/v1. Both hosts give the same answers.

Limits

600 reads and 60 writes a minute per token. Over the limit you get 429. Scopes: read for GET, write for settings, deliver for replay.

Bins

GET    https://webhooks.xyz/api/v1/bins
POST   https://webhooks.xyz/api/v1/bins
GET    https://webhooks.xyz/api/v1/bins/{bin}
DELETE https://webhooks.xyz/api/v1/bins/{bin}
PUT    https://webhooks.xyz/api/v1/bins/{bin}/response   {"status": 503, "headers": {"Content-Type": "text/plain"}, "body": "down", "delay_ms": 0}
PUT    https://webhooks.xyz/api/v1/bins/{bin}/scenario   {"scenario": "fail", "fail_minutes": 10}   # normal | timeout | fail | echo

Requests

GET    https://webhooks.xyz/api/v1/bins/{bin}/requests?method=POST&verify_status=failed&since=1759924800&path_prefix=/orders&q=ORD-20481&after=0&limit=100
GET    https://webhooks.xyz/api/v1/bins/{bin}/requests/{id}
DELETE https://webhooks.xyz/api/v1/bins/{bin}/requests

Each request has headers, body (or body_b64 for binary), verify (status ok | failed | unchecked, scheme, detail), resp_status and rule_hit.

Signature verification

GET    https://webhooks.xyz/api/v1/bins/{bin}/secrets                 # also lists the schemes
POST   https://webhooks.xyz/api/v1/bins/{bin}/secrets                 {"scheme": "stripe", "secret": "whsec_…", "label": "live"}
DELETE https://webhooks.xyz/api/v1/bins/{bin}/secrets/{id}

Destinations and deliveries

GET    https://webhooks.xyz/api/v1/bins/{bin}/destinations
POST   https://webhooks.xyz/api/v1/bins/{bin}/destinations            {"url": "https://api.acme.example/hooks", "method": "POST", "headers": {"Authorization": "Bearer …"}, "timeout_s": 10}
DELETE https://webhooks.xyz/api/v1/bins/{bin}/destinations/{id}
POST   https://webhooks.xyz/api/v1/bins/{bin}/destinations/{id}/pause?paused=1
GET    https://webhooks.xyz/api/v1/bins/{bin}/deliveries?status=failed&request_id=…
POST   https://webhooks.xyz/api/v1/bins/{bin}/deliveries/{id}/replay
POST   https://webhooks.xyz/api/v1/bins/{bin}/deliveries/replay-failed

Import and export

GET    /api/v1/bins/{id}/export                 → {format, bin: {name, response, destinations}, requests: [...]}   (settings + every request)
POST   /api/v1/import   {"source": "webhook-site", "token": "…", "api_key": "…", "bin_id": null}
                        {"source": "hookdeck", "api_key": "…"} | {"source": "pipedream", "api_key": "…", "source_id": "dc_…"}
                        {"source": "svix-play", "token": "…"} | {"source": "json" | "har", "data": {…}, "bin_id": "…"}
                        → {id, bins: [{id, requests, url}], requests_n, settings_n, skipped, notes}
GET    /api/v1/imports                            → {imports: [...]}   (your import history with reports)

Status feeds

GET    /api/v1/status-feeds                      → {subscriptions: [...], vendors: [{slug, name, url}]}
POST   /api/v1/status-feeds   {"vendor": "github", "connection_ids": [12], "keywords": "Actions", "updates": true}
                              {"kind": "webhook", "name": "Acme Cloud", "connection_ids": [12], "min_impact": "major"}  → inbound_url
DELETE /api/v1/status-feeds/{id}

Private Relay

A destination with "mode": "relay" is called by whx relay from inside your network; private addresses are accepted. Relay tokens (whr_…) are per bin and deliver-only. Private Relay has the agent protocol.

POST   /api/v1/bins/{id}/destinations         {"url": "http://10.0.3.12:8080/webhooks", "mode": "relay"}
GET    /api/v1/bins/{id}/relay/tokens         → {"tokens": [{id, name, prefix, connected, last_seen, agent}]}
POST   /api/v1/bins/{id}/relay/tokens         {"name": "vpc-eu-1"} → {"token": "whr_…", "run": "whx relay --token whr_…"}   (token shown once)
DELETE /api/v1/bins/{id}/relay/tokens/{tid}

Public: a bin without a token

POST /api/v1/public/bins needs no token and no body. It makes an anonymous bin and answers 201 {"id", "url", "page", "expires_at", "retention"}. The bin is deleted 24 hours after its last request. Browsers may call it only from the sites we list (today mailt.in). After some bins from one network in an hour the answer is 429 {"error": "captcha_required", "new_url": "https://webhooks.xyz/new"}: open new_url, where a short check makes the bin.

Connections and actions

GET    https://webhooks.xyz/api/v1/connections
POST   https://webhooks.xyz/api/v1/connections                        {"kind": "slack", "label": "#revenue", "config": {"webhook_url": "https://hooks.slack.com/services/…"}}
DELETE https://webhooks.xyz/api/v1/connections/{id}
GET    https://webhooks.xyz/api/v1/bins/{bin}/actions
POST   https://webhooks.xyz/api/v1/bins/{bin}/actions                 {"trigger": "request", "connection_id": 1, "filter": "body.type is payment_intent.succeeded", "template": "Paid {{ body.data.object.amount_received }} by {{ body.data.object.receipt_email }}"}
POST   https://webhooks.xyz/api/v1/bins/{bin}/actions/{id}/test       # fires once against the newest request
DELETE https://webhooks.xyz/api/v1/bins/{bin}/actions/{id}
GET    https://webhooks.xyz/api/v1/me

Filters and templates

A filter is one condition per line: field op value. Ops: is, contains, starts, exists. Fields: method, path, verify, header:x-github-event, query:source, body.data.object.currency. All lines must match.

A template is text with {{ field }} placeholders from the same fields, plus {{ bin_url }}, {{ request_id }}, {{ event.error }}, {{ event.failures_in_a_row }} and {{ destination }} on delivery triggers. No code runs in templates.

Example: nightly list of failed deliveries

curl -s -H "Authorization: Bearer $WHX_TOKEN" \
  "https://webhooks.xyz/api/v1/bins/k7f3abc/deliveries?status=failed" | jq '.deliveries[] | {id, request_id, error}'