Navigation

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