Documentation

API Base URL: https://hookpipe.app

Quick Start

1. Create an API Key

API keys authenticate you for hook management. The key is shown once at creation — save it.

curl -X POST https://hookpipe.app/api/auth/keys \
  -H "Content-Type: application/json" \
  -d '{"name": "my-key"}'

2. Create a Hook

A hook defines a receive endpoint, optional transform rules, and an optional destination URL. Leave out destinationUrl for a store-only hook: payloads are kept and you read them back through the payloads API, including by long-polling.

curl -X POST https://hookpipe.app/api/hooks \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "github-events",
    "destinationUrl": "https://your-app.com/webhook",
    "transformConfig": {
      "fieldMapping": {
        "repo": "repository.name",
        "actor": "sender.login"
      }
    }
  }'

The response includes an id — that's your hook ID for the receive URL.

3. Point Your Webhooks Here

Use https://hookpipe.app/hooks/YOUR_HOOK_ID as the webhook URL in whatever service you're integrating.

# Example: test it manually
curl -X POST https://hookpipe.app/hooks/YOUR_HOOK_ID \
  -H "Content-Type: application/json" \
  -d '{"repository": {"name": "my-repo"}, "sender": {"login": "dev"}}'

Guides

Detailed walkthroughs for specific use-cases. View all guides →


Transform Rules

Transform rules are optional. If you don't set transformConfig, the payload is forwarded as-is.

Field Mapping

Extract nested fields into a flat structure using dot notation:

"fieldMapping": {
  "repo": "repository.name",      // payload.repository.name → result.repo
  "actor": "sender.login",        // payload.sender.login → result.actor
  "action": "action"              // payload.action → result.action (top-level)
}

Filtering

Only forward payloads that match conditions. If a filter doesn't match, the payload is dropped (HTTP 200 returned to sender, nothing forwarded).

"filters": [
  {"field": "action", "operator": "equals", "value": "opened"},
  {"field": "repository.name", "operator": "contains", "value": "prod"}
]

Supported operators: equals, notEquals, contains, notContains, exists, notExists, greaterThan, lessThan.

Field Renaming

"renames": {
  "old_field_name": "new_field_name"
}

Adding Static Values

Add constant values by including them directly in fieldMapping. Use a non-dot-notation key with a literal string:

"fieldMapping": {
  "repo": "repository.name",
  "source": "github",
  "version": "1.0"
}

API Reference

Authentication

All /api/* endpoints require an API key in the Authorization header:

Authorization: Bearer YOUR_API_KEY

The webhook receive endpoint (POST /hooks/:hookId) is public — no auth required.

POST /api/auth/keys

Create a new API key. No authentication required.

BodyTypeDescription
namestringA label for the key

POST /api/hooks

Create a new hook. Requires auth.

BodyTypeDescription
namestringHook name
destinationUrlstringOptional. URL to forward transformed payloads to. Omit it for a store-only hook: nothing is forwarded, payloads are stored and read back via the payloads API
transformConfigobjectOptional. See Transform Rules

GET /api/hooks

List all hooks. Requires auth.

GET /api/hooks/:id

Get a specific hook. Requires auth.

DELETE /api/hooks/:id

Delete a hook. Requires auth.

GET /api/hooks/:id/logs

Get request logs for a hook. Requires auth.

QueryTypeDefaultDescription
limitnumber100Max entries to return
offsetnumber0Skip entries

GET /api/hooks/:id/payloads

List stored payloads for a hook (most recent first), or long-poll for new ones. Requires auth.

QueryTypeDefaultDescription
limitnumber50Max entries (max 200)
offsetnumber0Skip entries (ignored with after)
afterstring—Cursor: return only payloads newer than this payload ID, oldest first. An empty string starts from the oldest stored payload
waitnumber0With after: seconds to hold the request until a new payload arrives (max 30). Returns an empty list when the wait expires

Response includes rawBody (original payload) and transformedBody (after transform rules, if any). With after the response echoes it, and total is the number of entries returned.

Long-polling. A machine behind NAT, or anything that can't expose a URL, can receive payloads without a destination: create the hook without destinationUrl, then loop on after + wait. The server keeps no per-client state; the client only remembers the last payload ID it saw.

# Seed the cursor with the newest payload ID (empty string = start from the oldest)
AFTER=$(curl -s https://hookpipe.app/api/hooks/YOUR_HOOK_ID/payloads?limit=1 \
  -H "Authorization: Bearer YOUR_API_KEY" | jq -r '.data[0].id // ""')

# Each request returns as soon as a newer payload is stored, or after 30s with an empty list
while true; do
  RESP=$(curl -s "https://hookpipe.app/api/hooks/YOUR_HOOK_ID/payloads?after=$AFTER&wait=30" \
    -H "Authorization: Bearer YOUR_API_KEY")
  echo "$RESP" | jq -c '.data[]'            # handle each new payload here
  NEXT=$(echo "$RESP" | jq -r '.data[-1].id // empty')
  [ -n "$NEXT" ] && AFTER=$NEXT
done

GET /api/hooks/:id/payloads/:payloadId

Get a single stored payload with full body. Requires auth.

POST /api/hooks/:id/payloads/:payloadId/replay

Re-run the transform → forward pipeline for a stored payload. Useful when the original forward failed (destination was down) and you want to retry. Requires auth.

The replayed request includes an X-Replay: true header so your destination can distinguish replays from original deliveries.

POST /hooks/:hookId

Receive a webhook. Public — no auth required. This is the URL you give to external services.

Payload is validated, stored, transformed (if rules exist), and forwarded to the hook's destination URL. Returns a payloadId for later inspection or replay.

GET /health

Health check. Returns {"status": "healthy"} with uptime.


Signature Validation

Verify that incoming webhooks are authentic by checking their HMAC signatures. Supports GitHub, Stripe, and generic HMAC providers.

Configure Signature Validation

curl -X PATCH https://hookpipe.app/api/hooks/HOOK_ID/signature \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "enabled": true,
    "provider": "github",
    "secrets": ["your-webhook-secret"],
    "debug": true
  }'

Supported providers:

Free tier: 1 signing secret per hook. Pro tier: Up to 5 secrets for key rotation — all secrets are tried until one matches.

Set "debug": true during setup to see detailed error messages in 401 responses. Set to false for production (generic error only).

Disable Signature Validation

curl -X DELETE https://hookpipe.app/api/hooks/HOOK_ID/signature \
  -H "Authorization: Bearer YOUR_API_KEY"

Retry Policy

Failed forwarding attempts are retried automatically with exponential backoff.

Configure Retry Policy (Pro)

curl -X PATCH https://hookpipe.app/api/hooks/HOOK_ID/retry \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"maxRetries": 7, "intervalMs": 5000}'

Limits & Notes

Feature Free Pro ($9/mo)
Rate limit 20 req/min per hook 1,000 req/min per hook
Hooks 5 Unlimited
Retries 3 (fixed 1s backoff) Up to 10 (configurable interval)
Signature secrets 1 per hook 5 per hook (key rotation)
Log retention 7 days 30 days
Payload retention 7 days 30 days