API Reference
CatchHook provides a REST API for programmatic access to endpoints, ingress events, and tunnel connections.
Base URL: https://catchhook.app/api/v1
Authentication
All API requests require a Bearer token in the Authorization header:
Authorization: Bearer <your-api-token>
Create API tokens in Account Settings. Tokens have two dimensions of access control:
Token level
| Level | Access | Created from |
|---|---|---|
| Account | All workspaces and resources in the account | Account Settings |
| Workspace | Only the specific workspace's resources | Workspace management page |
CLI tokens (created during catchhook login) are always account-scoped.
Permission scopes
| Scope | Access |
|---|---|
read |
List and view endpoints, events, actions, and activity |
write |
Create, update, delete resources; replay events |
tunnel |
Connect to tunnel WebSocket and report deliveries |
Endpoints
Verify authentication
GET /api/v1/auth/verify
Returns the current user and account information. Useful for verifying your token is valid.
Response:
{
"user": { "id": 1, "email": "you@example.com" },
"account": { "id": 1, "name": "Acme Corp" }
}
List endpoints
GET /api/v1/endpoints
Returns all endpoints accessible to the authenticated user, including both webhook and email endpoints.
Response:
{
"data": [
{
"id": "ep_abc123",
"name": "GitHub Webhooks",
"kind": "webhook",
"custom_id": null,
"provider": "github",
"provider_config": {},
"webhook_url": "https://listen.catchhook.app/hooks/ep_abc123",
"email_address": null,
"tunnel_active": false,
"created_at": "2026-05-01T12:00:00Z",
"updated_at": "2026-05-01T12:00:00Z"
},
{
"id": "ep_def456",
"name": "Transactional Emails",
"kind": "email",
"custom_id": null,
"provider": null,
"provider_config": {},
"webhook_url": null,
"email_address": "billing@in.catchhook.app",
"tunnel_active": false,
"created_at": "2026-05-01T12:00:00Z",
"updated_at": "2026-05-01T12:00:00Z"
}
]
}
Create an endpoint
POST /api/v1/endpoints
Body (webhook endpoint):
{
"endpoint": {
"name": "My Endpoint",
"kind": "webhook",
"provider": "github"
}
}
Body (email endpoint):
{
"endpoint": {
"name": "Transactional Emails",
"kind": "email",
"email_local_part": "billing"
}
}
| Parameter | Description |
|---|---|
name |
Human-readable label (required) |
kind |
Endpoint type: webhook (default) or email |
provider |
Webhook only. One of github, stripe, shopify, slack, or twilio. Stores the intended webhook source for this endpoint. Only CLI preset providers (github, stripe) enable the CLI --provider signature-config workflow; Shopify, Slack, and Twilio configuration remains available through the web application and API. |
email_local_part |
Email only. The local part of the generated email address (before the @). Must be lowercase alphanumeric with dots, hyphens, or underscores (2–50 characters). If omitted, a random local part is generated. |
Provider detection of incoming webhooks happens automatically at ingest for all webhook endpoints, regardless of whether provider is set. Setting provider stores your intended webhook source. For CLI preset providers (github, stripe), it also enables the --provider workflow for automatic signature config creation.
For email endpoints, provider is not applicable — email provider detection happens automatically based on email headers.
Get an endpoint
GET /api/v1/endpoints/:id
Returns details for a single endpoint. The response includes kind, webhook_url (webhook endpoints), and email_address (email endpoints).
List events for an endpoint
GET /api/v1/endpoints/:endpoint_id/requests
Returns ingress events (both webhook requests and email events) received by the specified endpoint. Events are returned in reverse chronological order.
Query parameters:
| Parameter | Description | Default |
|---|---|---|
limit |
Number of events (1–100) | 25 |
offset |
Number of events to skip | 0 |
Response:
{
"data": [
{
"id": "req_abc123",
"type": "webhook",
"method": "POST",
"path": "/hooks/checkout",
"status": 200,
"content_type": "application/json",
"ip_address": "1.2.3.4",
"size": 1024,
"requested_at": "2026-05-01T12:00:00Z",
"detected_provider": "stripe",
"provider_event_data": { "event_type": "invoice.paid" }
},
{
"id": "eml_def456",
"type": "email",
"method": "EMAIL",
"path": "billing@in.catchhook.app",
"status": null,
"content_type": null,
"ip_address": null,
"size": 4096,
"requested_at": "2026-05-01T11:30:00Z",
"from": "sender@example.com",
"to": "billing@in.catchhook.app",
"subject": "Order Confirmation",
"detected_provider": "sendgrid",
"authentication": { "spf": "PASS", "dkim": "PASS", "dmarc": "PASS" }
}
],
"meta": {
"total": 42,
"limit": 25,
"offset": 0
}
}
Get a single event
GET /api/v1/endpoints/:endpoint_id/requests/:id
Returns full details for a single event, including the complete body. For email events, the full response includes text_body, html_body, email_headers, attachment_metadata, and authentication_results.
For webhook events, the full response also includes response capture fields: response_status_sent, response_headers_sent, response_body_sent, and response_source. These record the exact HTTP response that was returned to the webhook sender.
Delete an event
DELETE /api/v1/endpoints/:endpoint_id/requests/:id
Scope required: write
Permanently deletes a single ingress event.
Delete events in bulk
DELETE /api/v1/endpoints/:endpoint_id/requests/destroy_all
Scope required: write
Deletes all events for the endpoint. Optional query parameters:
| Parameter | Description |
|---|---|
before |
Delete events received before this ISO 8601 timestamp |
after |
Delete events received after this ISO 8601 timestamp |
Replay an event
POST /api/v1/endpoints/:endpoint_id/requests/:id/replay
Scope required: write
Re-sends an event to a target URL.
For webhook events, the API currently supports exact replay only — the original method, headers, and body are forwarded as-is with optional identity and signature handling. Request modification parameters (body editing, header/query overrides) are available in the web UI and will be added to the API in a future release.
For email events, the replay sends the normalized JSON payload as a POST request to the target URL.
Body:
{
"target_url": "https://your-app.com/webhooks",
"identity_mode": "original",
"signature_mode": "preserve"
}
| Parameter | Options | Default | Description |
|---|---|---|---|
target_url |
Any valid URL | (required) | Destination for the replayed event |
identity_mode |
original, regenerate |
original |
regenerate creates a provider-aware delivery identity (JSON bodies only, requires payment method). For Shopify it changes only X-Shopify-Webhook-Id, preserving the merchant event ID and body resource IDs. Webhook events only. |
signature_mode |
preserve, strip, resign |
preserve |
preserve keeps the captured signature, strip removes provider signature headers, and resign computes a fresh signature using the matching active provider configuration (requires payment method and signing support). Webhook events only. |
CatchHook adds replay metadata headers to every outbound request: X-Catchhook-Replay, X-Catchhook-Original-Request, and X-Catchhook-Replay-Time.
Replay Cases
Replay Cases preserve an encrypted, endpoint-owned replay scenario beyond source-event retention and require at least one deterministic status or JSON-subset assertion.
| Operation | Method and path | Scope |
|---|---|---|
| List cases | GET /api/v1/endpoints/:endpoint_id/replay_cases |
read |
| Get case | GET /api/v1/endpoints/:endpoint_id/replay_cases/:id |
read |
| Create case | POST /api/v1/endpoints/:endpoint_id/replay_cases |
write |
| Update case | PATCH /api/v1/endpoints/:endpoint_id/replay_cases/:id |
write |
| Delete case | DELETE /api/v1/endpoints/:endpoint_id/replay_cases/:id |
write |
| Run case | POST /api/v1/endpoints/:endpoint_id/replay_cases/:id/run |
write |
| List retained runs | GET /api/v1/endpoints/:endpoint_id/replay_case_runs |
read |
| Get retained run | GET /api/v1/endpoints/:endpoint_id/replay_case_runs/:id |
read |
Create and update bodies use a replay_case object:
{
"replay_case": {
"source_id": "req_abc123",
"name": "Payment succeeds",
"description": "Regression for the completed-payment handler",
"target_url": "https://your-app.com/webhooks",
"options": {
"identity_mode": "regenerate",
"signature_mode": "resign"
},
"assertions": {
"status": { "class": "2xx" },
"json_subset": { "received": true }
}
}
}
Updates require the latest lock_version; stale edits return 409. Case runs always use the saved fixed target and return 202. Supply an Idempotency-Key header when creating a run. Reusing the key for the same case version returns the existing run; conflicting reuse returns 409.
Case and retained-run lists accept limit (1–100) and an opaque cursor. Use the response's meta.next_cursor to retrieve the next stable page.
Run states are queued, running, passed, assertion_failed, transport_failed, blocked, and unknown. unknown means CatchHook cannot prove whether the downstream received the request, so it never retries that run automatically.
Delete an endpoint
DELETE /api/v1/endpoints/:id
Scope required: write
Permanently deletes an endpoint and all its events.
Signature Configs
Create a signature config
POST /api/v1/endpoints/:endpoint_id/signature_configs
Scope required: write
Creates a signature verification config for a webhook endpoint. On Pro/Business plans, any provider is allowed. On free plans, this is restricted to provider-mode endpoints where the sig config provider matches the endpoint's provider.
Not applicable to email endpoints — email authentication (SPF, DKIM, DMARC) is handled at the transport level by AWS SES.
Body:
{
"signature_config": {
"provider": "github",
"secret": "whsec_your_secret_here",
"enabled": true
}
}
| Parameter | Description |
|---|---|
provider |
github, stripe, shopify, slack, twilio, or generic_hmac |
secret |
The webhook signing secret |
enabled |
Whether verification is active (default true) |
Conflict handling: If a config for the same provider already exists, the API returns 409 Conflict. Pass ?force=true to overwrite the existing config.
Response (201):
{
"data": {
"id": 1,
"provider": "github",
"enabled": true
}
}
Endpoint Actions
List actions
GET /api/v1/endpoints/:endpoint_id/actions
Returns all actions configured on the endpoint, ordered by position.
Create an action
POST /api/v1/endpoints/:endpoint_id/actions
Scope required: write
Body:
{
"endpoint_action": {
"name": "Forward to staging",
"enabled": true,
"failure_policy": "continue",
"steps": [
{
"step_type": "forward",
"name": "Staging server",
"config": {
"url": "https://staging.example.com/webhooks",
"timeout_seconds": 30,
"retry_count": 3
}
}
]
}
}
Update an action
PATCH /api/v1/endpoints/:endpoint_id/actions/:id
Scope required: write
Delete an action
DELETE /api/v1/endpoints/:endpoint_id/actions/:id
Scope required: write
Get an action and recent runs
GET /api/v1/endpoints/:endpoint_id/actions/:id
Returns the action configuration and its 10 most recent execution traces, including
step-by-step results.
Events
List recent events
GET /api/v1/events
Scope required: read
Returns recent activity events for the account, including both webhook and email ingress. Useful for building integrations that react to CatchHook activity.
Query parameters:
| Parameter | Description | Default |
|---|---|---|
limit |
Number of events (1–200) | 50 |
since |
ISO 8601 timestamp | 24 hours ago |
Event types:
| Type | Description |
|---|---|
webhook.received |
A webhook was received at a webhook endpoint |
email.received |
An email was received at an email endpoint |
forwarding.failed |
An Action Forward attempt failed |
alert.triggered |
An alert condition was detected |
Response:
{
"data": [
{
"type": "webhook.received",
"occurred_at": "2026-05-01T12:00:00Z",
"endpoint_id": "ep_abc123",
"request_id": "req_def456",
"data": {
"method": "POST",
"path": "/hooks/stripe",
"content_type": "application/json",
"size": 1024
}
},
{
"type": "email.received",
"occurred_at": "2026-05-01T11:30:00Z",
"endpoint_id": "ep_ghi789",
"request_id": "eml_jkl012",
"data": {
"from": "sender@example.com",
"to": "billing@in.catchhook.app",
"subject": "Order Confirmation",
"size": 4096
}
}
],
"meta": { "count": 2 }
}
Tunnel
Connect (authenticated)
POST /api/v1/tunnel/connect
Scope required: tunnel
Body:
{
"endpoint_id": "ep_abc123"
}
Works for webhook endpoints. Exchanges your API token for a one-time WebSocket ticket (valid for 30 seconds).
Response:
{
"ticket": "a1b2c3...",
"expires_in": 30
}
Connect (anonymous / temporary endpoint)
POST /api/v1/tunnel/connect_anonymous
No authentication required. Uses the endpoint's tunnel_key instead.
Body:
{
"tunnel_key": "tk_xyz789"
}
Response:
{
"ticket": "a1b2c3...",
"expires_in": 30,
"endpoint_id": "ep_abc123"
}
Report delivery
POST /api/v1/tunnel/delivery_reports
Scope required: tunnel
Reports the result of a local tunnel delivery back to CatchHook. Used by the CLI to power tunnel health metrics and alerts.
Body:
{
"endpoint_id": "ep_abc123",
"webhook_request_id": "req_def456",
"target_url": "http://localhost:3000/webhooks",
"status_code": 200,
"response_message": "OK",
"response_time_ms": 42
}
The webhook_request_id field accepts a captured webhook request ID.
Discover unresolved delivery gaps
GET /api/v1/tunnel/gaps?endpoint_ids[]=ep_abc123
Scope required: tunnel
Returns durable open, reconnected, or partial gap summaries for the requested authorized webhook endpoints, ordered oldest first. Gap summaries contain counts and timing metadata, but not request headers or bodies.
Fetch undelivered webhooks
The legacy short-window request remains supported and returns the original { "data": [...] } response:
GET /api/v1/tunnel/undelivered?endpoint_ids[]=ep_abc123&minutes=120
Newer clients can page a durable gap using an opaque keyset cursor:
GET /api/v1/tunnel/undelivered?endpoint_ids[]=ep_abc123&gap_id=tgap_def456&limit=100&cursor=...
Gap-aware responses add meta with the outage boundary, total and pending counts, the next cursor, and whether normal event retention truncated the gap. A since timestamp can replace gap_id, but it cannot predate the account's retained request data.
Report recovery progress
POST /api/v1/tunnel/gaps/tgap_def456/recovery
Scope required: tunnel
{
"outcome": "completed",
"attempted_count": 42,
"succeeded_count": 41,
"failed_count": 1
}
Allowed outcomes are completed, partial, skipped, and interrupted. CatchHook re-queries persisted delivery reports before marking a gap recovered, so a claimed successful outcome cannot resolve requests that were not actually delivered.
Rate limits
API endpoints are rate-limited to prevent abuse:
| Endpoint | Limit |
|---|---|
| Tunnel connect (authenticated) | 30 requests / 60 seconds |
| Tunnel connect (anonymous) | 10 requests / 60 seconds |
When rate-limited, the API returns 429 Too Many Requests with a retry_after field.
Error responses
All errors follow a consistent format:
{
"error": "not_found"
}
Common status codes:
| Code | Meaning |
|---|---|
| 401 | Invalid or missing API token |
| 403 | Token doesn't have the required scope |
| 404 | Resource not found |
| 422 | Validation error |
| 429 | Rate limited |