API Reference

Webhooker API

Complete reference for the Webhooker REST API. Base URL: https://webhooker.erk.im

Quick Start

Get webhooks flowing in three steps.

1

Create a Source

A source gives you a unique ingest URL. Point your webhook provider (Stripe, GitHub, Shopify, etc.) at this URL.

POST /api/sources
curl -X POST https://webhooker.erk.im/api/sources \
  -H "Authorization: Bearer wh_live_..." \
  -H "Content-Type: application/json" \
  -d '{"name": "stripe-prod"}'

# Response
{
  "id": "src_01abc123",
  "name": "stripe-prod",
  "slug": "stripe-prod-a1b2c3",
  "ingest_url": "https://ingest.webhooker.erk.im/stripe-prod-a1b2c3",
  "created_at": "2026-05-02T00:00:00Z"
}
2

Create a Destination

A destination is any HTTPS endpoint that should receive the forwarded webhook.

POST /api/destinations
curl -X POST https://webhooker.erk.im/api/destinations \
  -H "Authorization: Bearer wh_live_..." \
  -H "Content-Type: application/json" \
  -d '{"name": "my-api", "url": "https://api.yourapp.com/webhooks"}'

# Response
{
  "id": "dst_01xyz456",
  "name": "my-api",
  "url": "https://api.yourapp.com/webhooks",
  "created_at": "2026-05-02T00:00:00Z"
}
3

Create a Connection

A connection links a source to a destination. You can add filters and transformations here too.

POST /api/connections
curl -X POST https://webhooker.erk.im/api/connections \
  -H "Authorization: Bearer wh_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "stripe-to-my-api",
    "source_id": "src_01abc123",
    "destination_id": "dst_01xyz456"
  }'

# Webhooks sent to ingest.webhooker.erk.im/stripe-prod-a1b2c3
# are now delivered to api.yourapp.com/webhooks

Authentication

All API requests require a Bearer token in the Authorization header.

Authorization: Bearer wh_live_xxxxxxxxxxxxxxxxxxxx

Where to find your key: Dashboard Settings API Keys. Keys are prefixed with wh_live_ for production and wh_test_ for test mode.

Core Concepts

Sources

A unique ingest URL for an inbound webhook provider. One source per external service (e.g. one for Stripe, one for GitHub).

Destinations

Your HTTPS endpoints that should receive the forwarded webhook payloads.

Connections

A route linking a source to a destination. Each connection can have independent filters, transformations, and retry rules.

Events

One delivery unit per connection per inbound request. An event tracks the full lifecycle from receipt to successful delivery.

Requests

The raw inbound HTTP request captured at the source. One request may fan out into multiple events across connections.

Attempts

Each time Webhooker tries to deliver an event to a destination. Failed attempts trigger automatic retry logic.

Sources

Sources capture inbound webhooks from external providers. Each source gets a unique ingest URL at https://ingest.webhooker.erk.im/{source_slug}.

GET/api/sources

List all sources for the authenticated account.

Response

{
  "data": [
    {
      "id": "src_01abc123",
      "name": "stripe-prod",
      "slug": "stripe-prod-a1b2c3",
      "ingest_url": "https://ingest.webhooker.erk.im/stripe-prod-a1b2c3",
      "created_at": "2026-05-02T00:00:00Z"
    }
  ],
  "total": 1
}
POST/api/sources

Create a new source.

Request

{
  "name": "github-webhooks"
}

Response

{
  "id": "src_01def456",
  "name": "github-webhooks",
  "slug": "github-webhooks-d4e5f6",
  "ingest_url": "https://ingest.webhooker.erk.im/github-webhooks-d4e5f6",
  "created_at": "2026-05-02T00:00:00Z"
}
GET/api/sources/:id

Retrieve a single source by ID.

PUT/api/sources/:id

Update a source name.

Request

{ "name": "stripe-prod-v2" }
DELETE/api/sources/:id

Delete a source. All associated connections will also be deleted.

Destinations

Destinations are the HTTPS endpoints that receive forwarded webhook payloads.

GET/api/destinations

List all destinations.

Response

{
  "data": [
    {
      "id": "dst_01xyz456",
      "name": "my-api",
      "url": "https://api.yourapp.com/webhooks",
      "created_at": "2026-05-02T00:00:00Z"
    }
  ],
  "total": 1
}
POST/api/destinations

Create a new destination.

Request

{
  "name": "production-api",
  "url": "https://api.yourapp.com/webhooks"
}
GET/api/destinations/:id

Retrieve a single destination by ID.

PUT/api/destinations/:id

Update a destination URL or name.

Request

{
  "name": "production-api-v2",
  "url": "https://api.yourapp.com/v2/webhooks"
}
DELETE/api/destinations/:id

Delete a destination. Connections referencing this destination will also be removed.

Connections

Connections route webhooks from a source to a destination. Each connection can independently configure filters, transformations, and retry behavior.

GET/api/connections

List all connections. Optionally filter with ?source_id= or ?destination_id=.

Response

{
  "data": [
    {
      "id": "con_01ghi789",
      "name": "stripe-to-my-api",
      "source_id": "src_01abc123",
      "destination_id": "dst_01xyz456",
      "enabled": true,
      "filter_rules": null,
      "transformation": null,
      "retry_strategy": "exponential",
      "created_at": "2026-05-02T00:00:00Z"
    }
  ],
  "total": 1
}
POST/api/connections

Create a new connection between a source and destination.

Request

{
  "name": "stripe-to-my-api",
  "source_id": "src_01abc123",
  "destination_id": "dst_01xyz456",
  "retry_strategy": "exponential"
}
GET/api/connections/:id

Retrieve a single connection by ID.

PUT/api/connections/:id

Replace a connection's full configuration.

Request

{
  "name": "stripe-to-my-api",
  "source_id": "src_01abc123",
  "destination_id": "dst_01xyz456",
  "retry_strategy": "linear",
  "filter_rules": { "body.type": { "$eq": "payment.completed" } }
}
PATCH/api/connections/:id

Partially update a connection (e.g. toggle enabled, update filter only).

Request

{ "enabled": false }
DELETE/api/connections/:id

Delete a connection. Existing events for this connection are retained.

Events

Events represent a single delivery of a webhook through a connection. Query events to inspect delivery status or replay failures.

GET/api/events

List events. Supports ?connection_id=, ?status=, ?limit=, ?cursor= for pagination.

Response

{
  "data": [
    {
      "id": "evt_01jkl012",
      "connection_id": "con_01ghi789",
      "status": "delivered",
      "attempts": 1,
      "created_at": "2026-05-02T00:00:00Z",
      "delivered_at": "2026-05-02T00:00:01Z"
    }
  ],
  "next_cursor": null
}
GET/api/events/:id

Retrieve a single event with full payload and all attempt history.

Response

{
  "id": "evt_01jkl012",
  "connection_id": "con_01ghi789",
  "request_id": "req_01mno345",
  "status": "delivered",
  "payload": { "event": "payment.completed", "data": { "amount": 4999 } },
  "attempts": [
    {
      "id": "att_01pqr678",
      "status": "success",
      "response_status": 200,
      "duration_ms": 23,
      "attempted_at": "2026-05-02T00:00:01Z"
    }
  ]
}
POST/api/events/:id/retry

Manually trigger a retry for a failed or held event. Returns the new attempt object.

Bookmarks

Bookmarks save a specific webhook payload so you can replay it repeatedly during development or testing without triggering the real provider.

GET/api/bookmarks

List all bookmarks for the account.

Response

{
  "data": [
    {
      "id": "bkm_01stu901",
      "name": "stripe-payment-completed",
      "event_id": "evt_01jkl012",
      "connection_id": "con_01ghi789",
      "created_at": "2026-05-02T00:00:00Z"
    }
  ]
}
POST/api/bookmarks

Create a bookmark from an existing event.

Request

{
  "name": "stripe-payment-completed",
  "event_id": "evt_01jkl012"
}

API Keys

Manage programmatic access keys for the account.

POST/api/api-keys

Create a new API key. The secret value is only returned once at creation time.

Request

{ "name": "ci-deployment" }

Response

{
  "id": "key_01vwx234",
  "name": "ci-deployment",
  "key": "wh_live_xxxxxxxxxxxxxxxxxxxx",
  "created_at": "2026-05-02T00:00:00Z"
}
DELETE/api/api-keys/:id

Revoke and delete an API key immediately.

Usage

Retrieve event and request counts for billing and monitoring.

GET/api/usage

Get usage metrics for the current billing period.

Response

{
  "period_start": "2026-05-01T00:00:00Z",
  "period_end": "2026-05-31T23:59:59Z",
  "events_total": 8423,
  "events_delivered": 8401,
  "events_failed": 22,
  "requests_total": 8423
}

Webhook Payload

When Webhooker delivers an event to your destination, the request body is the original (or transformed) payload. The following headers are added on every delivery:

HeaderDescription
x-webhooker-event-idUnique ID of the event (evt_...)
x-webhooker-request-idID of the originating ingest request (req_...)
x-webhooker-attempt-countNumber of delivery attempts so far (starts at 1)
x-webhooker-signatureHMAC-SHA256 of the raw body signed with your connection secret
x-webhooker-timestampUnix timestamp (seconds) of this delivery attempt
Example delivery request
POST https://api.yourapp.com/webhooks
Content-Type: application/json
x-webhooker-event-id: evt_01jkl012
x-webhooker-request-id: req_01mno345
x-webhooker-attempt-count: 1
x-webhooker-signature: sha256=abc123def456...
x-webhooker-timestamp: 1746144000

{
  "event": "payment.completed",
  "data": { "amount": 4999, "currency": "usd" }
}

Signature verification: Compute HMAC-SHA256 over the raw request body using your connection secret. Compare to the value in x-webhooker-signature (strip the sha256= prefix before comparing).

Filters

Filter rules let you selectively route events through a connection. Rules are evaluated against the inbound request using dot-notation paths (body.*, headers.*, query.*).

OperatorDescriptionExample
$eqExact equality{ "$eq": "payment.completed" }
$neNot equal{ "$ne": "payment.refunded" }
$containsString contains substring{ "$contains": "payment" }
$existsField presence check{ "$exists": true }
$gtGreater than (numeric){ "$gt": 100 }
$ltLess than (numeric){ "$lt": 1000 }
$inValue is in array{ "$in": ["usd", "eur"] }
Filter rule example — only route completed USD payments over $10
{
  "body.event": { "$eq": "payment.completed" },
  "body.data.currency": { "$eq": "usd" },
  "body.data.amount": { "$gt": 1000 }
}

Transformations

Transformations reshape the payload before delivery. Each mapping copies or renames a field using dot-notation paths. The from path is resolved against the incoming payload; to sets the output field.

Transformation schema
{
  "mappings": [
    { "from": "body.data.amount",   "to": "amount"   },
    { "from": "body.data.currency", "to": "currency" },
    { "from": "body.event",         "to": "type"     }
  ]
}

// Input payload:
// { "event": "payment.completed", "data": { "amount": 4999, "currency": "usd" } }

// Transformed payload delivered to destination:
// { "amount": 4999, "currency": "usd", "type": "payment.completed" }

Set a connection's transformation field when creating or updating the connection. Set to null to pass the payload through unchanged.

Retries

Webhooker automatically retries failed deliveries. A delivery is considered failed when the destination returns a non-2xx status code or times out (>30 s). Choose a strategy per connection.

Linear backoff

Waits a fixed interval between each attempt. Use for destinations where consistent spacing matters.

retry_strategy: "linear"
// Attempts at: +1 m, +2 m, +3 m ... (up to 50)

Exponential backoff

Doubles the wait time after each failure with jitter, reducing thundering herd during outages.

retry_strategy: "exponential"
// Attempts at: +30 s, +1 m, +2 m, +4 m ... (up to 50)

Set retry_strategy: "none" to disable retries. Events that exhaust all attempts move to status failed. You can always trigger a manual retry via POST /api/events/:id/retry.

Reseller API

Reseller accounts can provision isolated sub-accounts and inspect aggregate usage. Each sub-account is fully isolated with its own sources, destinations, and API keys. Requires a reseller-tier plan.

GET/api/reseller/sub-accounts

List all sub-accounts owned by the reseller.

Response

{
  "data": [
    {
      "id": "sub_01yz5678",
      "name": "customer-acme",
      "plan": "starter",
      "created_at": "2026-05-02T00:00:00Z"
    }
  ]
}
POST/api/reseller/sub-accounts

Provision a new sub-account. Returns an API key scoped to that sub-account.

Request

{
  "name": "customer-acme",
  "plan": "starter"
}

Response

{
  "id": "sub_01yz5678",
  "name": "customer-acme",
  "plan": "starter",
  "api_key": "wh_live_xxxxxxxxxxxxxxxxx",
  "created_at": "2026-05-02T00:00:00Z"
}
PATCH/api/reseller/sub-accounts/:id

Update a sub-account's name or plan.

Request

{ "plan": "team" }
DELETE/api/reseller/sub-accounts/:id

Delete a sub-account and all its resources. This action is irreversible.

GET/api/reseller/usage

Aggregate usage metrics across all sub-accounts for the current billing period.

Response

{
  "period_start": "2026-05-01T00:00:00Z",
  "period_end":   "2026-05-31T23:59:59Z",
  "sub_accounts": 4,
  "events_total": 142500,
  "events_delivered": 141983,
  "events_failed": 517
}

MCP Server

Webhooker ships an official Model Context Protocol (MCP) server so AI agents (Claude, Cursor, Copilot, etc.) can manage your webhook infrastructure directly.

Installation

Run the MCP server in a single command — no installation required:

npx (recommended)
WEBHOOKER_API_KEY=wh_live_... npx @webhooker/mcp

Claude Desktop config

Add to your claude_desktop_config.json:

{
  "mcpServers": {
    "webhooker": {
      "command": "npx",
      "args": ["@webhooker/mcp"],
      "env": {
        "WEBHOOKER_API_KEY": "wh_live_..."
      }
    }
  }
}

Available tools

The MCP server exposes all major API operations as callable tools:

list_sources
get_source
create_source
update_source
delete_source
list_destinations
get_destination
create_destination
update_destination
delete_destination
list_connections
create_connection
update_connection
delete_connection
list_events
get_event
retry_event
get_usage
list_sub_accounts
create_sub_account
delete_sub_account
get_reseller_usage

Error Codes

All errors follow RFC 7807 with a JSON body containing error and message fields.

StatusMeaningCommon cause
400Bad RequestInvalid or missing request body fields
401UnauthorizedMissing or invalid API key
403ForbiddenKey lacks permission for the requested resource
404Not FoundResource ID does not exist or belongs to another account
409ConflictDuplicate name or slug; idempotency key conflict
500Internal Server ErrorUnexpected error — contact support with the request ID
Error response shape
HTTP/1.1 404 Not Found
Content-Type: application/json

{
  "error": "not_found",
  "message": "Source src_01abc123 does not exist"
}

Ready to integrate?

Create your first source in under 2 minutes. No credit card required.