# Webhook Jar > Webhook Jar (https://webhookjar.com) is a private webhook inbox for developers. Open an inbox, point any service that sends webhooks (Stripe, GitHub, Shopify, Slack, Twilio or anything that makes an HTTP request) at its post URL, and every request lands live with its headers, raw body, sender, event name and signature check. The REST API at https://webhookjar.com/v1 makes inboxes from code, waits for the next webhook, and reads, deletes, forwards and replays requests, which makes webhook flows testable in CI. This file is the complete API reference in Markdown, written for AI assistants and coding tools. It is generated from the same settings the API runs on, so its numbers are current. Fetch it at https://webhookjar.com/llms.txt. The error-handling part alone is at https://webhookjar.com/docs/errors.md. The human version is https://webhookjar.com/docs. Updated: 2026-10-12 ## The service - **Inbox:** a private URL that catches requests. The post URL (`https://webhookjar.com/in/...`) can only send; reading needs the view URL or an API key. - **Without an account:** an inbox lasts 48 hours and takes up to 500 requests. - **Free** (sign in with Google): 3 inboxes, 7 days of history, 5,000 requests a day, 1 API key, 1,000 API calls a day. - **Solo** ($9 a month): 25 inboxes, 90 days of history, 50,000 requests a day, forwarding with retries, replay, 5 API keys, 25,000 API calls a day. - **Signature checks:** add a signing secret to an inbox and each request is checked: Stripe (`stripe-signature`), GitHub (`x-hub-signature-256`), Shopify (`x-shopify-hmac-sha256`), Paddle (`paddle-signature`), Slack (`x-slack-signature`), Twilio (`x-twilio-signature`), SendGrid (`x-twilio-email-event-webhook-signature`), Standard Webhooks (`webhook-signature`). The result is `verified`, `failed`, or `null` with no secret set. - **Handshakes:** with `answer.challenges` on, Slack, Meta and Twitch verification requests are answered for you. - **Custom answer:** each inbox answers with a status (200, 201, 202, 204, 400, 401, 403, 404, 409, 410, 422, 429, 500, 502, 503), an optional body and an optional delay (0, 2, 10, 30 seconds). - **Forwarding (Solo):** each request is also sent to your own public http or https address. A failed delivery is retried 1, 5, 15, 30, 70 minutes after each failure. - **Status:** https://webhookjar.com/status and https://webhookjar.com/status.json. ## Basics - **Base URL:** `https://webhookjar.com/v1` - **Authentication:** `Authorization: Bearer wj_live_...` on every call. Keys are made at https://webhookjar.com/account/api. Keep keys out of front-end code: a key reads every inbox on its account. - **Format:** JSON in and out. Send `Content-Type: application/json` with a body. - **Times:** UTC, ISO 8601, with milliseconds on requests (`2026-09-29T14:32:08.412Z`). - **Ids:** `ib_...` for an inbox, `rq_...` for a request. Treat them as opaque strings. ## Endpoints ### POST /v1/inboxes Makes an inbox, kept for as long as the account is. A plan holds 3 inboxes on Free and 25 on Solo; at the limit the call answers `403 plan` and nothing is made. Delete an inbox to make room. - Body (optional): `name`, string, up to 60 characters. - Answer: `201` with an inbox object. ```bash curl -X POST "https://webhookjar.com/v1/inboxes" \ -H "Authorization: Bearer $WJ_KEY" \ -H "Content-Type: application/json" \ -d '{"name": "Stripe test inbox"}' ``` The inbox object: ```json { "id": "ib_8k2m", "name": "Stripe test inbox", "post_url": "https://webhookjar.com/in/p3jd9xq7wlm2", "view_url": "https://webhookjar.com/inbox/hq81czt0v4", "kept": true, "expires_at": null, "requests": 0, "last_request_at": null, "answer": { "status": 200, "body": "", "delay": 0, "challenges": true }, "forward": { "url": "", "on": false }, "created_at": "2026-09-29T14:30:00Z" } ``` Give `post_url` to the sender. `view_url` opens the inbox in a browser. ### GET /v1/inboxes Every open inbox on the account, busiest first, up to 200. - Query (optional): `kept=true` for kept inboxes only, `kept=false` for the ones that expire. - Answer: `200` with `{"inboxes": [inbox, ...]}`. ### GET /v1/inboxes/{id} One inbox. Answer: `200` with an inbox object. ### PATCH /v1/inboxes/{id} Changes an inbox. Send only what changes. - `name`: string. - `answer`: object with any of `status` (one of 200, 201, 202, 204, 400, 401, 403, 404, 409, 410, 422, 429, 500, 502, 503), `body` (string, up to 2,000 characters), `delay` (one of 0, 2, 10, 30 seconds) and `challenges` (boolean). - `forward` (Solo): object with `url` (public http or https) and `on` (boolean). - Answer: `200` with the inbox object. ```bash curl -X PATCH "https://webhookjar.com/v1/inboxes/ib_8k2m" \ -H "Authorization: Bearer $WJ_KEY" \ -H "Content-Type: application/json" \ -d '{"answer": {"status": 500}}' ``` ### DELETE /v1/inboxes/{id} Deletes the inbox and every request in it. Its post URL stops answering at once. Answer: `204`, no body. ### GET /v1/inboxes/{id}/next Holds the call open until a request lands, then answers with it. This is the call for tests. - `wait` (optional): seconds to wait, 0 to 25. Default 25. - `after` (optional): a request id. Answers the first request after it straight away if there is one, so nothing is missed between calls. Without it, only a request that lands after the call counts. - Answer: `200` with a request object, or `204` with no body when nothing arrives in time. The test loop: 1. `POST /v1/inboxes` and keep `id` and `post_url`. 2. Give `post_url` to the provider and trigger the event. 3. `GET /v1/inboxes/{id}/next?wait=25`. 4. On `200`, assert on the request. On `204`, call again or fail the test. 5. For the next event, pass the last request id as `after`. ```bash curl "https://webhookjar.com/v1/inboxes/ib_8k2m/next?wait=25" \ -H "Authorization: Bearer $WJ_KEY" ``` ### GET /v1/inboxes/{id}/requests The inbox's requests, newest first, without bodies. - `limit` (optional): 1 to 100. Default 50. - `before` (optional): a request id; pass the `next` from the last page. - `provider` (optional): one sender only, like `stripe`, `github` or `shopify`. - Answer: `200` with `{"requests": [short request, ...], "next": "rq_..." or null}`. A short request: `id`, `at`, `method`, `provider`, `event`, `headline`, `size`, `signature`. ### GET /v1/requests/{id} Everything caught, as it came. Answer: `200` with a request object: ```json { "id": "rq_91fd2", "inbox_id": "ib_8k2m", "at": "2026-09-29T14:32:08.412Z", "method": "POST", "path": "", "query": {}, "headers": { "content-type": "application/json", "stripe-signature": "t=1790690728,v1=5257a8..." }, "body": "{\"id\":\"evt_1Q...\",\"type\":\"invoice.paid\"}", "body_encoding": "utf8", "truncated": false, "size": 2231, "content_type": "application/json", "ip": "3.18.12.63", "country": "US", "provider": "stripe", "event": "invoice.paid", "headline": "Invoice paid: $49.00 from Northwind", "signature": "verified", "checks": [{ "state": "ok", "title": "Signature matches", "detail": "" }], "answered": { "status": 200, "body": "" } } ``` - `body` is the raw body as a string; when it is not text, it is base64 and `body_encoding` is `base64`. - `path` is anything after the post URL (`/in/{token}/this/part`). - `headers` keys are lower case; repeated headers are joined with `, `. ### DELETE /v1/requests/{id} Deletes one request. Answer: `204`, no body. ### POST /v1/requests/{id}/replay (Solo) Sends the request again to the inbox's forward address, exactly as it came, and waits up to 10 seconds for your server. Answer: `200` with: ```json { "delivery": { "status": "ok", "http": 200, "ms": 142, "url": "https://api.example.com/hooks", "error": null } } ``` ### POST /v1/inboxes/{id}/samples Delivers a real provider webhook into the inbox: the provider's own headers, body and signature, stored as if it had been posted, so every check runs (and forwarding, on Solo). Signed with the inbox's own signing secret for that provider when one is set, so `signature` is `verified`; otherwise with a published sample secret. The request is marked `"sample": true` and answers `201` with it in full, plus `signed_with` (`yours` or `sample`). Up to 20 a minute per inbox. Body: `{"provider": "stripe", "event": "invoice.paid"}`. `GET /v1/samples` lists every provider and event: - `github`: push, pull_request.opened, issues.opened, workflow_run.completed, release.published, ping - `stripe`: checkout.session.completed, invoice.paid, invoice.payment_failed, customer.subscription.deleted, charge.refunded, payment_intent.succeeded - `shopify`: orders/create, orders/paid, customers/create - `twilio`: message.received, message.delivered, message.undelivered, call.completed (form fields, signed over the URL) - `sendgrid`: delivered, open, bounce, batch (signed with a published sample key; SendGrid alone holds yours) - `slack`: app_mention, message.channels, reaction_added, member_joined_channel, slash_command (form fields), block_actions (interactivity) - `standard` (OpenAI, Standard Webhooks): response.completed, batch.completed, fine_tuning.job.succeeded ### GET /v1/usage The plan, what was used today and what is left. Also the quickest way to check a key. ```json { "plan": "solo", "calls": { "today": 1602, "refused_today": 7, "day_limit": 25000, "minute_limit": 300 }, "inboxes": { "kept": 6, "kept_limit": 25 }, "captured_today": { "requests": 4210, "limit": 50000 }, "history_days": 90, "resets_at": "2026-09-30T00:00:00Z" } ``` ## Error handling ### The error shape Every API answer that is not 2xx is JSON with one `error` object: ```json { "error": { "code": "not_found", "message": "No inbox ib_zz01 on this account" } } ``` - Branch on `error.code`. It is stable and never changes meaning. - Show `error.message` to a person, or log it. It is plain English, names the field or value at fault, and may be reworded. - Unknown fields may be added to answers later. Ignore what you do not know. ### Every API error code | HTTP | code | When | What to do | |---|---|---|---| | 400 | `bad_request` | A parameter or the body is missing, not JSON, or the wrong type or range. The message names the field. | Fix the request. Do not retry it unchanged. | | 401 | `no_key` | No `Authorization: Bearer wj_live_...` header, or the key was rolled or revoked. | Get a key at https://webhookjar.com/account/api. Do not retry with the same key. | | 403 | `plan` | A Solo feature (replay, forwarding) called with a Free key, or all of the plan's inboxes (3 on Free, 25 on Solo) are in use. | Delete an inbox, upgrade at https://webhookjar.com/pricing, or skip the feature. Do not retry unchanged. | | 404 | `not_found` | No such inbox or request on this account, an inbox that expired or was closed, or no such endpoint. | Check the id. Ids are only visible to the account that owns them. | | 405 | `method` | The endpoint exists but not with this method. The `Allow` header lists the methods it takes. | Use a method from `Allow`. | | 429 | `slow_down` | Over the per-minute or per-day call limit, or over 300 new inboxes an hour. | Wait the `Retry-After` seconds, then retry. Refused calls do not count against the day. | | 500 | `server` | Something failed on our side. It is logged. | Retry after 1, 2, then 4 seconds. Stop after 3 tries. | Success codes that are not errors: - `201` from `POST /v1/inboxes`: the inbox was made. - `204` from `DELETE`: done, no body. - `204` from `GET /v1/inboxes/{id}/next`: nothing arrived within `wait` seconds. Not an error: call again, or fail your test. ### Retry rules, in short 1. Retry only `429`, `500` and, on catch URLs, `503`. 2. On `429` and `503`, wait exactly the `Retry-After` header (seconds), then retry once. 3. On `500`, back off 1, 2, 4 seconds; stop after 3 tries. 4. Never retry `400`, `401`, `403`, `404` or `405` without changing the request. 5. `POST /v1/inboxes` is not idempotent: a retry after a timeout can make a second inbox. List inboxes first if that matters. 6. `DELETE` is safe to retry: a second call answers `404 not_found`. ### Limits and the headers that report them | Plan | Keys | Calls a day | Calls a minute | New inboxes by API an hour | |---|---|---|---|---| | Free | 1 | 1,000 | 30 | 300 | | Solo | 5 | 25,000 | 300 | 300 | Calls are counted per account, across all its keys. The day starts again at 00:00 UTC. Every answer carries: - `X-RateLimit-Limit`: calls allowed a minute. - `X-RateLimit-Remaining`: calls left this minute. - `X-Calls-Day-Limit`: calls allowed a day. - `X-Calls-Today`: calls made today. - `Retry-After` (on `429` only): seconds to wait. ### Errors, endpoint by endpoint Every endpoint can also answer `401 no_key`, `405 method`, `429 slow_down` and `500 server`. - `POST /v1/inboxes`: `400` when the body is not JSON or `name` is not a string. `403 plan` when the plan's inboxes are all in use (3 on Free, 25 on Solo). `429` over 300 new inboxes an hour. - `GET /v1/inboxes`: no others. - `GET /v1/inboxes/{id}`: `404` when the inbox is not yours, expired or closed. - `PATCH /v1/inboxes/{id}`: `404` as above. `400` for an unknown field (only `name`, `answer` and `forward` change), `name` not a string, `answer` not an object, `answer.status` not one of 200, 201, 202, 204, 400, 401, 403, 404, 409, 410, 422, 429, 500, 502, 503, `answer.delay` not one of 0, 2, 10, 30 seconds, `answer.body` not a string or over 2,000 characters, `forward` not an object, or a forward address that is not public http or https (a private network, localhost, or this site). `403 plan` for `forward` on Free. - `DELETE /v1/inboxes/{id}`: `404` as above. - `GET /v1/inboxes/{id}/next`: `404` as above. `400` when `wait` is not a whole number from 0 to 25, or `after` is not a request id like `rq_1a2b`. `204` when nothing arrives in time. - `GET /v1/inboxes/{id}/requests`: `404` as above. `400` when `limit` is not 1 to 100, or `before` is not a request id. - `GET /v1/requests/{id}`: `404` when the request is not in one of your inboxes. - `DELETE /v1/requests/{id}`: `404` as above. - `POST /v1/requests/{id}/replay`: `404` as above. `403 plan` on Free. `400` when the inbox has no forward address. A `200` still reports a failed delivery: read `delivery.status` (`ok` or `failed`) and `delivery.error`. - `GET /v1/usage`: no others. Use it to check a key works. ### Catch URLs (no key) Senders post to `https://webhookjar.com/in/{post_token}` with any method, headers and body. These answers are for the sender, and use a flat shape, `{"error": "code", "message": "..."}`: | HTTP | error | When | What a sender should do | |---|---|---|---| | 200 | none | Caught and stored. The status and body can be changed per inbox with `answer`. | Nothing. | | 204 | none | A browser's CORS preflight (`OPTIONS`). Nothing is stored. | Nothing. | | 404 | `no_such_inbox` | No inbox at this address. | Check the URL. | | 410 | `inbox_expired` | The inbox passed its 48 hours. | Make a new inbox. | | 410 | `inbox_killed` | The inbox was closed. | Make a new inbox. | | 410 | `inbox_full` | An inbox without an account took its 500 requests. | Make a new inbox, or sign in to keep one. | | 429 | `slow_down` | Over 120 requests a minute into one inbox. `Retry-After: 60`. | Retry after a minute. | | 429 | `daily_limit` | The account's daily allowance (5,000 on Free, 50,000 on Solo) is used. `Retry-After` counts to 00:00 UTC. | Retry after midnight UTC. | | 503 | `busy` | Very busy for a moment. Nothing was saved. `Retry-After: 5`. | Send it again. Every major provider does this on its own. | A body over 1 MB is stored cut to 1 MB, and the request shows `truncated: true`. ## MCP server - **URL:** `https://webhookjar.com/mcp` (Streamable HTTP, JSON-RPC 2.0). Send the API key as `Authorization: Bearer wj_live_...` (or `X-API-Key`). - **Claude Code:** `claude mcp add --transport http webhookjar https://webhookjar.com/mcp --header "Authorization: Bearer wj_live_..."` - **Tools:** `create_inbox`, `list_inboxes`, `get_inbox`, `wait_for_request`, `list_requests`, `send_sample`, `get_request`, `set_inbox_answer`, `set_forwarding`, `replay_request`, `delete_inbox`, `get_usage`. - **Refusals** come back as a tool result with `isError` and the API's error code (`no_key`, `plan`, `not_found`, `slow_down`). - **Guide:** https://webhookjar.com/docs/mcp ## Agent skill - [Webhook Jar agent skill](https://webhookjar.com/.well-known/agent-skills/webhookjar/SKILL.md): how agents use Webhook Jar. Index: https://webhookjar.com/.well-known/agent-skills/index.json ## Links - [API docs](https://webhookjar.com/docs): the same reference, with examples in curl, Node and Python. - [Error handling in Markdown](https://webhookjar.com/docs/errors.md) - [Pricing](https://webhookjar.com/pricing) - [Status](https://webhookjar.com/status) and [status.json](https://webhookjar.com/status.json) - [Get an API key](https://webhookjar.com/account/api) - [Support](https://webhookjar.com/support)