WebhookTool HTTP API

WebhookTool gives you a URL that records every HTTP request sent to it, and every DNS lookup of a name under it, and every email sent to an address of its own. Everything the dashboard does is also available over this API, for scripts, test suites and AI agents. The base URL is https://webhooktool.com, and request and response bodies are JSON unless noted.

Quick start

Create an inbox. The response holds its id, its webhook URL in urls.ingest and its secret, which is returned only this once.

curl -s -X POST https://webhooktool.com/api/v1/inboxes

Send it anything.

curl -X POST 'https://whks.dev/<id>/orders?source=test' \
  -H 'Content-Type: application/json' \
  -d '{"hello": "world"}'

Read what arrived.

curl -s -H 'Authorization: Bearer <secret>' https://webhooktool.com/api/v1/inbox/captures

Open https://webhooktool.com/#<secret> in a browser to see the same inbox in the dashboard.

MCP for AI agents

AI agents can use WebhookTool through its MCP server at https://webhooktool.com/mcp. It needs no account or sign-in, because the agent creates its own inboxes and passes each inbox's secret to the tools that read or change it. To let an agent use an inbox you already have, give it the inbox's private link.

Add it to Claude Code.

claude mcp add --transport http webhooktool https://webhooktool.com/mcp

Or to Codex.

codex mcp add webhooktool --url https://webhooktool.com/mcp

In VS Code, add "webhooktool": {"type": "http", "url": "https://webhooktool.com/mcp"} under servers in mcp.json. In Cursor, add "webhooktool": {"url": "https://webhooktool.com/mcp"} under mcpServers in mcp.json. Other agents take it as a remote MCP server with this URL, over Streamable HTTP.

ToolDoes
create_inboxCreate an inbox, with any settings. Returns its URLs, dashboard link and secret.
get_inboxGet the inbox.
update_inboxChange its settings.
delete_inboxDestroy it and everything in it.
wait_for_captureWait for the next capture, or the next one matching a search, and return it.
list_capturesList or search captures, without their headers and bodies.
get_captureGet one capture with its headers and body. Long bodies come in parts.
get_capture_asGet a request as code that sends it again.
check_signatureCheck an Amazon SNS or PayPal signature with the sender's certificate, a Google Pub/Sub, Microsoft Graph or Azure Event Grid token with its issuer's published keys, an App Store notification with Apple's root certificate, or a decoded JWT from Google, Microsoft or Apple.
set_capture_noteSet or remove a note.
delete_capturesDelete captures by ID, by search, or all of them.

Anyone who knows an inbox's URL can send to it, so the server tells agents to treat captures as data and not to follow instructions in them.

Inboxes and secrets

  • An inbox has an id, a UUID that is part of its webhook URL https://whks.dev/<id>. Anyone who knows the webhook URL can send requests to it, but reading them needs the secret. To operate and maintain the service, the operators may also access inbox data, as the privacy page explains.
  • The secret, 26 lowercase letters and digits, reads, changes and deletes the inbox. Send it as a bearer token: Authorization: Bearer <secret>. It is returned only when the inbox is created and cannot be recovered. The link https://webhooktool.com/#<secret> opens the inbox in the dashboard, and https://webhooktool.com/#<secret>/<capture-id> opens one capture.
  • An inbox and everything in it are deleted retention_seconds after the inbox was created: 1 day unless you choose between 5 minutes and 3 days. expires_at says when. You can change the time later, but not to less than the inbox's current age.
  • An inbox keeps its newest capture_limit captures: 50 unless you choose 1 to 100, and at most 10.0 MiB in total. Older captures are deleted to make room.
  • How many inboxes you can have at a time is limited. Destroying an inbox makes room right away.
  • How many inboxes you can create within 1 day is limited too, and destroyed and expired inboxes count toward it. Once you reach it, you can create the next inbox 1 day after your latest one.

Sending requests

Any method, path, query string, headers and body sent to https://whks.dev/<id> or a path below it are recorded. Bodies are recorded up to 1.0 MiB. Anything beyond that is cut off and the capture is marked "truncated": true.

The inbox answers with the response in its settings, plus an X-Request-Id header holding the capture's id and X-Robots-Tag: noindex, nofollow, which keeps inbox URLs out of search engines. To answer one request with another status, start the path with a status code from 200 to 599, as in https://whks.dev/<id>/404/anything.

Browsers that load the webhook URL themselves, as a page, a frame, a script, an image or the like, get the body as text/plain; charset=utf-8 whatever content_type says, so an inbox can't show visitors a web page, start a download or run as a script. Calls from a page's scripts with fetch() or XMLHttpRequest, and requests from anything that isn't a browser, such as webhook senders and curl, get the response exactly as set.

With cors on, responses allow every origin, and CORS preflight requests (OPTIONS with Origin and Access-Control-Request-Method headers) are answered with 204 and allow the requested method and headers.

Other answers from the webhook URL are plain text.

StatusMeaningRecorded
404There is no inbox with that id, or it expired.No
403The request asked for a service worker script, or one of WebhookTool's own pages asked for a script, worker or style sheet. Inboxes do not serve those.No
429Requests are reaching the inbox too fast. Retry after Retry-After seconds.No
429Too many responses with a delay are waiting to be sent for the inbox.Yes
503The server is busy. Retry after Retry-After seconds.No
507The server's storage is full, and no inbox had older requests to give up. When storage is full, the inboxes of the network that stores the most lose their oldest captures first to make room.No

DNS lookups

Lookups of any name under <id>.1.whks.dev are recorded in the inbox and answered with no records. The inbox's urls.dns holds that pattern. Lookups count toward the same rate limit as requests, and lookups above it are answered but not recorded.

dig hello.<id>.1.whks.dev

A lookup is a capture with "kind": "dns", "method": "DNS" and a dns object. Its remote_ip is the address of the resolver that sent the lookup, usually not that of the system that made it. When the resolver's address lies in a network's published ranges, insights.network names that network, such as google-public-dns for Google Public DNS.

Emails

Emails to any address at <id>.email.whks.dev, such as hello@<id>.email.whks.dev, are recorded in the inbox. The inbox's urls.email holds that pattern. Messages up to 1.0 MiB are recorded, and longer ones are cut off. Mail for an unknown inbox is refused. Emails count toward the same rate limit as requests, and those above it are refused for the time being, so mail servers send them again later.

An email is a capture with "kind": "email", "method": "SMTP" and an email object. Its proto is SMTP, ESMTP, or ESMTPS after STARTTLS, or UTF8SMTP and UTF8SMTPS when the sender used SMTPUTF8, and its remote_ip is the address of the sending mail server. headers lists the message's header fields in the order they came, files its attachments and other parts by the part's index, and body is the raw message, downloaded as message/rfc822. Getting one capture also returns text and html, the message's plain text and HTML, decoded.

Each email is checked with SPF, DKIM and DMARC when it arrives, and email.auth holds the results: whether the sending server may send mail for the envelope sender's domain, whether each DKIM signature verifies, and whether the message passes the DMARC policy of its From address's domain. The checks use the DNS records the sender's domains had then. Only DMARC ties the From address to its domain's owner.

Endpoints

Every endpoint except POST /api/v1/inboxes needs the inbox's secret as a bearer token, and acts on that inbox. Browsers can only call endpoints that change something from WebhookTool's own pages. Such requests from other sites are refused with 403. Scripts and other programs that are not browsers are not affected.

EndpointDoes
POST /api/v1/inboxesCreate an inbox.
GET /api/v1/inboxGet the inbox.
PATCH /api/v1/inboxChange its settings.
DELETE /api/v1/inboxDestroy it and everything in it.
GET /api/v1/inbox/capturesList or search captures.
DELETE /api/v1/inbox/capturesDelete all captures, or those matching a search.
GET /api/v1/inbox/captures/{id}Get one capture with its body.
DELETE /api/v1/inbox/captures/{id}Delete one capture.
GET /api/v1/inbox/captures/{id}/bodyDownload the raw body.
GET /api/v1/inbox/captures/{id}/files/{n}Download an uploaded file.
GET /api/v1/inbox/captures/{id}/as/{format}Get the request as code that sends it again.
PUT /api/v1/inbox/captures/{id}/noteSet or remove a note.
POST /api/v1/inbox/captures/{id}/signatureCheck a signature with the sender's certificate or its issuer's published keys.
GET /api/v1/inbox/exportDownload captures as JSON, CSV or HAR.

POST /api/v1/inboxes

Creates an inbox and answers 201 Created with the inbox, including its secret. The body is optional and may hold any settings to start with.

curl -s -X POST https://webhooktool.com/api/v1/inboxes \
  -H 'Content-Type: application/json' \
  -d '{"retention_seconds": 86400, "response": {"status": 202, "body": "accepted"}}'

GET /api/v1/inbox

Returns the inbox.

PATCH /api/v1/inbox

Changes any of the inbox's settings and returns the inbox. Fields you leave out keep their values, also inside response.

curl -s -X PATCH https://webhooktool.com/api/v1/inbox \
  -H 'Authorization: Bearer <secret>' \
  -H 'Content-Type: application/json' \
  -d '{"capture_limit": 100, "response": {"cors": true}}'

DELETE /api/v1/inbox

Destroys the inbox, so it and its captures are deleted right away and its secret stops working. Answers 204 No Content.

GET /api/v1/inbox/captures

Lists captures, without their bodies.

ParameterDefaultMeaning
page1Page number, from 1.
per_page50Captures per page, from 1 to 100.
sortnewestnewest or oldest first.
qOnly captures matching this search.
{
  "data": [
    {"id": "01a10371-afd5-7ba1-8019-b929b9af644a", "received_at": "2026-10-03T20:31:02.517Z", "kind": "http", "method": "POST", "url": "https://whks.dev/6ba141bf-2c4f-4601-a998-b0b878661de7/orders?source=test", "status": 200}
  ],
  "total": 1,
  "page": 1,
  "per_page": 50,
  "sort": "newest"
}

The captures in data have every field described under Capture except body. A search stops reading bodies after a set amount of data, and its result then has "partial": true.

DELETE /api/v1/inbox/captures

Deletes every capture, or with q only those matching a search. Answers {"deleted": 3, "partial": false}, where partial is true when the search stopped early, as when listing.

GET /api/v1/inbox/captures/{id}

Returns one capture, including its body.

DELETE /api/v1/inbox/captures/{id}

Deletes one capture. Answers 204 No Content.

GET /api/v1/inbox/captures/{id}/body

Downloads the raw body as application/octet-stream, or an email's raw message as message/rfc822 in an .eml file.

GET /api/v1/inbox/captures/{id}/files/{n}

Downloads the uploaded file whose index is n in the capture's files, or an email's part with that index, decoded.

GET /api/v1/inbox/captures/{id}/as/{format}

Returns the request as text that sends it again. format is curl, httpie, fetch (JavaScript), python (requests), powershell, or har for a HAR 1.2 document. The copy is addressed to the inbox's webhook URL with the same path and query, not to the host named in the original request, which the sender chose. Not available for DNS lookups or emails.

PUT /api/v1/inbox/captures/{id}/note

Sets the capture's note, up to 10000 characters, and returns the capture without its body. An empty note removes it.

curl -s -X PUT https://webhooktool.com/api/v1/inbox/captures/<capture-id>/note \
  -H 'Authorization: Bearer <secret>' \
  -H 'Content-Type: application/json' \
  -d '{"note": "retried by the provider"}'

POST /api/v1/inbox/captures/{id}/signature

Checks the signature of an Amazon SNS or PayPal capture with the signing certificate it names. The server downloads that certificate from Amazon's or PayPal's own servers, never from anywhere else, and keeps it for 12 hours. A PayPal signature also needs webhook_id, the ID PayPal gave the webhook. Signatures made with a shared secret, such as GitHub's or Stripe's, are checked in the dashboard instead, which keeps the secret in the browser. So are SendGrid's Signed Event Webhook (sendgrid, ECDSA P-256 over the timestamp and the body) and Discord's interactions (discord, Ed25519 over the timestamp and the body), with the public key SendGrid or Discord shows you. Discord's needs a browser that supports Ed25519 in WebCrypto.

Some senders sign without naming a certificate, and these are checked with keys their issuer publishes.

  • Google Pub/Sub push requests (google-oidc) carry a token from Google in Authorization, checked with Google's keys from www.googleapis.com. Its issuer must be accounts.google.com.
  • Microsoft Graph change notifications with resource data (microsoft-graph) carry tokens in validationTokens, and Azure Event Grid with Microsoft Entra authentication (azure-entra) carries one in Authorization. Both are checked with Microsoft identity's keys from login.microsoftonline.com, and their issuer must be Microsoft identity for a tenant. Each Graph token must also be issued to Microsoft Graph Change Tracking, the app 0bf30f3b-4a52-48df-9a82-234910c4a086.
  • App Store Server Notifications V2 (apple) carry the certificates that signed signedPayload. They must lead to Apple Root CA - G3, which WebhookTool carries, and carry the marks Apple puts on its signing certificates, so nothing is downloaded.

Tokens and certificates must have been valid when the request arrived, allowing 5 minutes of clock difference for tokens. A token shows who sent the request, but doesn't cover its body. The result's token holds what the token says, such as its issuer, subject, audience, the service account's email and whether Google marked it email_verified, the app that requested it, its tenant, issued_at, expires_at, and the key_id and keys_url of the key that checked it. For Apple, certificate is the certificate that signed the payload, and token holds the app bundle ID and the environment.

To check a JWT that decoded lists instead, send its from as layer, as in -d '{"layer": "body.id_token"}'. Only tokens that Google (accounts.google.com), Microsoft identity or Sign in with Apple (https://appleid.apple.com) issued with RS256 can be checked, with keys downloaded only from those issuers' fixed addresses. Any other token gets 400 with not_checkable.

curl -s -X POST https://webhooktool.com/api/v1/inbox/captures/<capture-id>/signature \
  -H 'Authorization: Bearer <secret>'
{"scheme": "aws-sns", "valid": true, "certificate": {"url": "https://sns.us-east-1.amazonaws.com/SimpleNotificationService-<id>.pem", "subject": "CN=sns.amazonaws.com", "issuer": "CN=Amazon RSA 2048 M01,O=Amazon,C=US", "not_after": "2027-06-21T23:59:59Z"}}

For PayPal, send the webhook ID as JSON, as in -H 'Content-Type: application/json' -d '{"webhook_id": "1JE4291016473214C"}'.

{"scheme": "google-oidc", "valid": true, "token": {"issuer": "https://accounts.google.com", "subject": "112233445566778899000", "audience": ["https://example.com/push"], "email": "pusher@my-project.iam.gserviceaccount.com", "email_verified": true, "key_id": "<key-id>", "keys_url": "https://www.googleapis.com/oauth2/v3/certs", "issued_at": "2026-10-12T10:00:00Z", "expires_at": "2026-10-12T11:00:00Z"}}

valid is false with a reason when the signature fails. mismatch means it doesn't match the certificate or key, certificate_url that the certificate it names isn't on the provider's own servers, so the provider didn't send it, certificate_unknown that it is on the provider's own domain but not where WebhookTool expects certificates, so it wasn't checked, certificate_expired that the certificate had expired when the request arrived, certificate_chain that Apple's certificates don't lead to Apple's root or lack Apple's marks, and unsupported that it uses a version that can't be checked. For tokens, token_expired means the token had expired or wasn't valid yet when the request arrived, key_unknown that its issuer doesn't publish the key that signed it, which also happens once an issuer drops an old key, issuer that the sender doesn't issue tokens like it, and token_app that a Microsoft Graph token wasn't issued to Microsoft Graph Change Tracking. A capture without such a signature, or whose body was cut off, gets 400 with not_checkable.

GET /api/v1/inbox/export

Downloads captures with their bodies, oldest first, as a file. format is json (an array of captures, the default), csv (one row per capture, with lists and objects as JSON, and a leading ' on text starting with =, +, - or @ so spreadsheets do not run it as a formula) or har (HAR 1.2, without DNS lookups and emails). With q, only captures matching a search are included.

curl -s -OJ -H 'Authorization: Bearer <secret>' 'https://webhooktool.com/api/v1/inbox/export?format=har'

Objects

Inbox

{
  "id": "6ba141bf-2c4f-4601-a998-b0b878661de7",
  "secret": "k7n2xq4vb6mz3wd5rf2hp7ty4c",
  "urls": {
    "ingest": "https://whks.dev/6ba141bf-2c4f-4601-a998-b0b878661de7",
    "dns": "*.6ba141bf-2c4f-4601-a998-b0b878661de7.1.whks.dev",
    "email": "*@6ba141bf-2c4f-4601-a998-b0b878661de7.email.whks.dev"
  },
  "created_at": "2026-10-03T20:25:36.123456Z",
  "expires_at": "2026-10-04T00:25:36.123456Z",
  "captures": 0,
  "settings": {
    "response": {
      "status": 200,
      "content_type": "text/plain; charset=utf-8",
      "body": "",
      "headers": [],
      "delay_ms": 0,
      "cors": false
    },
    "retention_seconds": 86400,
    "capture_limit": 50,
    "ip_mode": "full"
  }
}
FieldMeaning
idThe inbox ID, part of its webhook URL.
secretThe bearer token. Only in the answer that creates the inbox.
urls.ingestThe webhook URL.
urls.dnsThe names whose lookups are recorded.
urls.emailThe addresses whose emails are recorded.
created_atWhen the inbox was created, in UTC.
expires_atWhen the inbox and its captures will be deleted, in UTC.
capturesHow many captures the inbox holds.
settingsThe inbox's settings.
restrictionOnly while the operator restricts the inbox, which happens to inboxes that break the terms of use. http is true when new requests are refused with 403, dns when lookups are not recorded, email when new emails are refused, and response when requests get the default answer instead of the inbox's. notice says why, and expires_at when the restriction ends, if it does.

Settings

FieldDefaultAllowed values
response.status200200 to 599.
response.content_typetext/plain; charset=utf-8A header value of up to 256 bytes. Empty sends no Content-Type. Browsers that open the URL directly get text/plain instead.
response.bodyemptyUp to 16.0 KiB of text. A refresh <meta> tag is refused, since redirects are disabled to prevent abuse.
response.headers[]Up to 32 extra headers as {"name": "X-Signature", "value": "abc123"}, each value up to 4096 bytes, and names and values together up to 8.0 KiB. Headers such as Content-Type, Content-Length, Content-Disposition, Set-Cookie, Content-Security-Policy, WWW-Authenticate and X-Robots-Tag cannot be set, and neither can CORS headers such as Access-Control-Allow-Origin, which cors sends. Location and Refresh are refused too, since redirects are disabled to prevent abuse.
response.delay_ms00 to 30000. Waits this long before answering.
response.corsfalsetrue lets web pages on other sites call the webhook URL.
retention_seconds86400300 to 259200, counted from the inbox's creation. Not less than its current age.
capture_limit501 to 100.
ip_modefullHow much of each sender's IP address the inbox shows, either full, truncate (the /24 or /48 network) or none, which also leaves out the network the address lies in. The server keeps the whole address and its network either way, so a change applies to earlier captures too.

Capture

{
  "id": "01a10371-afd5-7ba1-8019-b929b9af644a",
  "received_at": "2026-10-03T20:31:02.517Z",
  "kind": "http",
  "method": "POST",
  "url": "https://whks.dev/6ba141bf-2c4f-4601-a998-b0b878661de7/orders?source=test",
  "host": "whks.dev",
  "path": "/orders",
  "query": [{"name": "source", "value": "test"}],
  "headers": [
    {"name": "Accept", "value": "*/*"},
    {"name": "Content-Length", "value": "18"},
    {"name": "Content-Type", "value": "application/json"},
    {"name": "Host", "value": "whks.dev"},
    {"name": "User-Agent", "value": "curl/8.7.1"}
  ],
  "proto": "HTTP/1.1",
  "remote_ip": "203.0.113.7",
  "content_type": "application/json",
  "content_length": 18,
  "body_size": 18,
  "status": 200,
  "body": "{\"hello\": \"world\"}",
  "body_encoding": "utf-8"
}
FieldMeaning
idThe capture ID, a UUIDv7.
received_atWhen the request arrived, in UTC.
kindhttp, dns or email.
method, url, host, protoAs received.
pathThe path after the inbox ID, as sent.
query, formQuery string and form fields as lists of name and value, in the order sent.
headersRequest headers as a list of name and value, sorted by name.
remote_ipThe sender's IP address, or the resolver's for a DNS lookup, or the sending mail server's for an email, as far as ip_mode shows it. Left out with none.
tlsversion, cipher_suite, server_name and alpn of an HTTPS connection.
timingHow long the request took to arrive, in milliseconds to the microsecond. tls_handshake_ms is the TLS handshake of an HTTPS connection, given for its first request only, and reused_connection is true for the requests after it. headers_ms runs from the request's first byte to the end of its headers, over HTTP/1 only. body_ms is how long reading the body took. Behind a reverse proxy they time the proxy's connection. Left out when nothing is known.
content_type, content_lengthThe request's Content-Type and Content-Length, with -1 when the length was not given.
body_sizeBytes of body recorded.
truncatedtrue when the body was longer than 1.0 MiB and cut off.
form_truncatedtrue when only the first fields or files of a large form are listed. The full form is in the body.
filesUploaded files as index, field, filename, content_type, size and detected_type, the type the start of the file's content shows, when it shows one.
statusThe status the webhook URL answered with.
noteYour note, if any.
insightsWhat a request names about itself, worked out when it arrived. source is the sender, such as github or stripe, event the event type, delivery the ID that retries of one delivery share, attempt the attempt number when the sender counts them and occurred_at when the event happened. signature names the scheme, header, algorithm and encoding of the request's signature, the content it signs such as {timestamp}.{body}, and signed_at. handshake marks a request sent to verify the URL, and for DNS lookups decoded lists the parts of the name that decode to text, each with from, encoding and text. ip_list tells whether the sender's address is on a list of addresses a provider publishes, with that list and listed. For a sender that has one, it is that sender's list, such as github, stripe or atlassian for their webhooks, aws for Amazon SNS and Data Firehose, google for Google Pub/Sub, or azure for Azure Event Grid and Microsoft Graph. Otherwise it is a webhook list that holds the address. network names the cloud, hosting provider, service, crawler, uptime monitor, VPN or anonymizer whose published ranges, or for providers such as ovhcloud or leaseweb the blocks it registered at the internet registries, hold the address, with its list, such as aws, google-cloud, azure, github-actions, hetzner, digitalocean, google-crawlers, bingbot, applebot, openai, uptimerobot, pingdom, nordvpn, proton-vpn, mullvad, pia, ivpn, ovpn, google-vpn, cloudflare-warp, tor or icloud-private-relay. A VPN's list names the addresses its servers take connections on, which is where most VPNs send their users' traffic from, or the ranges it sends from. A list of the addresses a provider uses for one service, such as its crawlers, names an address before the provider's whole network. It has the region when the provider names one, either a region such as eu-north-1 or a place such as Falkenstein, DE. For DNS lookups it names the resolver's network, such as google-public-dns. It is left out when ip_mode is none, and so are the network's mark and matches for network: and has:network. Both need the server to have the lists. The sender chose everything else these come from, so a matching signature is what shows who sent it.
insights.agentThe crawler, link preview, AI fetcher, uptime monitor, scanner or HTTP client the User-Agent names, with its id, such as googlebot, slack or curl, its kind, one of crawler, preview, ai, monitor, scanner or client, and the version of an HTTP client when it gives one. The dashboard shows an agent as verified when the sender's address is in the ranges its owner publishes for it, which insights.network names, such as google-crawlers, bingbot, applebot, openai, uptimerobot or pingdom.
body, body_encodingThe body, with body_encoding utf-8, or base64 when it is not valid UTF-8. Only when getting one capture and in JSON exports.
decodedWhat the body, form fields, query and headers hold encoded, decoded, such as a gzip body, JSON inside strings, base64 data, JWTs and Basic credentials. Each has from, encoding, format (json, text, jwt or basic), text, and user and password for Basic credentials. JWT signatures are not checked here, but a JWT that Google, Microsoft or Apple issued can be checked on request. Only when getting one capture.
dnsFor DNS lookups, the name, type, class, transport (udp or tcp), recursion_desired, and edns with udp_size and dnssec_ok when the query used EDNS.
emailFor emails, the envelope's helo, mail_from and rcpt_to, the header fields from, to, cc, reply_to, subject, date and message_id with encoded words decoded, and text and html with the index, content_type and size of the parts holding the plain text and HTML. auth holds the SPF, DKIM and DMARC results, with their names from RFC 8601. auth.spf has the result (none, neutral, pass, fail, softfail, temperror or permerror), the domain checked, the identity it came from (mailfrom, or helo when the envelope sender is empty), the record term that matched, the record holding it and a reason. With an ip_mode other than full, matched names only the term's mechanism, such as ip4. auth.dkim lists one object per signature, with the result (pass, fail, neutral, policy, temperror or permerror), the signing domain (d=), selector (s=), algorithm, identity (i=), length when the signature covers only that many bytes of the body, the RSA key_bits, testing when the key is marked as in testing, and a reason. It is left out for an unsigned message. auth.dmarc has the result (none, pass, fail, temperror or permerror), the From address's domain, the policy_domain whose record applies, its policy (none, quarantine or reject), testing, spf_aligned and dkim_aligned when SPF or a DKIM signature passed for a domain aligned with the From domain, and a reason. When the checks did not run, auth holds only a reason.
text, htmlAn email's plain text and HTML, decoded. Only when getting one capture.

query, form, form_truncated, files, tls, remote_ip, truncated, note, insights, decoded, dns, email, text and html are left out when they are empty or do not apply.

Use q to search, for example method:post status:4xx -path:/health.

curl -s -G -H 'Authorization: Bearer <secret>' https://webhooktool.com/api/v1/inbox/captures \
  --data-urlencode 'q=method:post status:4xx -path:/health'

Plain words match anywhere in a capture's method, URL, sender address, note, header, query and form names and values, file names, named sender and event, and body. All matching ignores case. Terms are combined with AND. Use OR, NOT or a leading -, parentheses, and double quotes around values with spaces. A search can be up to 1000 characters long with up to 32 terms. It stops reading bodies after a set amount of data and then reports "partial": true.

TermMatches
method:postThe HTTP method.
path:/orders url:exampleText in the path after the inbox ID, or in the full URL.
ip:203.0.113Text in the sender's address. from: works too, and also matches an email's sender address.
body:"order.created"Text in the body.
header:stripe header.user-agent:stripeText in any header, or in one header by name. query, query.name, form and form.name work the same way.
file:invoiceAn uploaded file name or field.
note:refundText in your note.
status:404 status:4xx status:>=500The status the webhook URL answered with.
size:>10kbBody size, with <, <=, >, >= and b, kb or mb.
after:now-2h before:2026-10-01Arrival time, as a date, a date and time such as 2026-10-01T14:30 (UTC), or relative to now with s, m, h, d or w.
source:stripe event:invoice.paidThe sender and event a request names, such as GitHub's push or Stripe's invoice.paid.
delivery:evt_123The delivery ID that retries of one delivery share.
network:aws network:falkenstein network:vpnRequests from the published or registered addresses of a cloud, hosting provider, service, crawler, uptime monitor, VPN or anonymizer, by its name, such as aws, azure, hetzner, ovhcloud, github-actions, google-public-dns, google-crawlers, openai, uptimerobot, nordvpn or tor, or by the region or place. network:vpn finds every VPN. Inboxes whose ip_mode is none match none.
agent:googlebot agent:preview has:agent has:clientThe agent the User-Agent names, by its id, name or kind. has:agent finds crawlers, link previews, AI fetchers, monitors and scanners, and has:client HTTP clients such as curl or Python Requests.
json.data.object.status:paid json.amount:>1000A field of a JSON body by its path, with numbers compared like size. A number in the path picks an array item, such as json.items.0.sku, and without one any item matches.
has:body has:files has:header.x-signatureCaptures that have a body, files, a form, a query, a note, a given header, query or form field, or a JSON field such as has:json.data.id. has:signature, has:retry, has:handshake, has:listed and has:network find signed requests, retries their sender marks, requests that verify the URL, requests from an address on a provider's published list and requests from a cloud's or network's published addresses. has:secret finds requests whose headers, query, form fields or body seem to hold a secret, such as a GitHub token, a live Stripe key, an AWS access key ID, a Slack token, a Google API key, a private key or a payment card number.
method:dns type:txt name:helloDNS lookups, by record type or by the name looked up.
method:smtp subject:invoice from:alice@ to:hello@Emails, by text in the subject, the sender's address or IP, or a recipient in To, Cc or the envelope. body: and plain words also search an email's decoded text and HTML.
spf:fail dkim:pass dmarc:rejectEmails, by the result of their SPF, DKIM or DMARC check. A result word (none, neutral, pass, fail, softfail, temperror, permerror, policy) matches exactly, so spf:fail leaves out softfail, and dkim:none finds unsigned emails. Any other value matches text in the domain checked, such as dkim:example.com. dmarc: also matches the From domain's policy: none, quarantine or reject.

Errors

Errors from the API have a JSON body with a machine-readable code and a message.

{"error": {"code": "invalid", "message": "capture_limit: must be between 1 and 100"}}
StatuscodeMeaning
400invalidA setting is not allowed, and message names the field.
400invalid_json, invalid_bodyThe body is not JSON of the expected shape, has unknown fields, or could not be read.
400invalid_parameter, invalid_queryA query parameter or the search is not valid.
400not_checkableThe capture carries no signature that a provider's certificate or published keys check, or its body was cut off.
401unauthorizedThe secret is missing or wrong, or its inbox expired or was destroyed.
403blockedThe operator blocked your network, and message says why.
404not_foundNo such capture, file, format or endpoint.
410taken_downThe operator took the inbox down, and message says why.
413too_largeThe request body is larger than 1 MiB.
429rate_limitedToo many requests. Retry after Retry-After seconds.
429inbox_limitYou have the most inboxes allowed at a time, or created the most allowed recently. Retry-After counts the seconds until you can create one.
503capacityThe server holds the most inboxes it allows.
503busyThe server is busy. Retry after Retry-After seconds.
500internalSomething went wrong on the server.
502certificate_unavailableThe provider's certificate or keys could not be downloaded.

Limits

WhatLimit
Inbox lifetime1 day, or up to 3 days
Captures per inbox50, or up to 100
Data per inbox10.0 MiB
Recorded body size1.0 MiB
Response body size16.0 KiB
Extra response headers8.0 KiB of names and values
RedirectsDisabled to prevent abuse, so Location and Refresh headers and refresh <meta> tags are refused