API Base URL: https://hookpipe.app
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"}'
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.
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"}}'
Detailed walkthroughs for specific use-cases. View all guides →
Transform rules are optional. If you don't set transformConfig, the payload is forwarded as-is.
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)
}
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.
"renames": {
"old_field_name": "new_field_name"
}
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"
}
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.
| Body | Type | Description |
|---|---|---|
| name | string | A label for the key |
POST /api/hooks
Create a new hook. Requires auth.
| Body | Type | Description |
|---|---|---|
| name | string | Hook name |
| destinationUrl | string | Optional. 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 |
| transformConfig | object | Optional. 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.
| Query | Type | Default | Description |
|---|---|---|---|
| limit | number | 100 | Max entries to return |
| offset | number | 0 | Skip entries |
GET /api/hooks/:id/payloads
List stored payloads for a hook (most recent first), or long-poll for new ones. Requires auth.
| Query | Type | Default | Description |
|---|---|---|---|
| limit | number | 50 | Max entries (max 200) |
| offset | number | 0 | Skip entries (ignored with after) |
| after | string | — | Cursor: return only payloads newer than this payload ID, oldest first. An empty string starts from the oldest stored payload |
| wait | number | 0 | With 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.
Verify that incoming webhooks are authentic by checking their HMAC signatures. Supports GitHub, Stripe, and generic HMAC providers.
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:
X-Hub-Signature-256 (SHA-256) with fallback to X-Hub-Signature (SHA-1)Stripe-Signature header with timestamp validation (rejects replays >5 min old)algorithm (e.g., "sha256") and headerName (e.g., "X-Signature")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).
curl -X DELETE https://hookpipe.app/api/hooks/HOOK_ID/signature \
-H "Authorization: Bearer YOUR_API_KEY"
Failed forwarding attempts are retried automatically with exponential backoff.
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}'
| 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 |
RateLimit-Limit, RateLimit-Remaining, and RateLimit-Reset headers