HookSync Documentation
Everything you need to integrate HookSync and deliver webhooks reliably — from your first API call to production-grade signature verification.
Quick start 5 min
From zero to your first delivered webhook in under 5 minutes. You need an account and an API key — both are free.
Step 1 — Register and create an API key
Sign up at /register, create an organization, then go to API Keys in the sidebar and generate a key. It will look like sk_abc123… (64 hex characters after the prefix).
Step 2 — Create a webhook endpoint
curl -X POST https://api.hooksync.net/api/v1/organizations/{org_id}/webhooks \
-H 'Authorization: Bearer sk_YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{
"name": "production",
"url": "https://your-server.com/webhooks",
"events": ["order.*"]
}'
# Response:
{
"data": {
"id": "wh_...",
"secret": "whsec_...", ← store this securely, shown only once
"events": ["order.*"],
"is_active": true
}
}whsec_…value is returned only once at creation time and is not stored in plain text. You'll use it to verify every incoming webhook.Step 3 — Send your first event
curl -X POST https://api.hooksync.net/ingest/{org_id} \
-H 'Authorization: Bearer sk_YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{
"event_type": "order.placed",
"payload": {
"order_id": "ord_123",
"amount": 9900,
"currency": "usd"
}
}'
# Response:
{
"data": {
"event_id": "evt_...",
"dispatched": 1 ← number of endpoints that received delivery jobs
}
}HookSync queues a delivery job, signs the payload with HMAC-SHA256, and POSTs it to your endpoint within milliseconds. Check the Events tab in the dashboard to see delivery status in real time.
Core concepts
HookSync operates on five primary entities. Understanding how they relate to each other makes the rest of the API obvious.
Organization
The top-level tenant. Every API key, webhook endpoint, event, and consumer belongs to exactly one organization. When you call the Ingest API you target an organization by its ID.
Webhook endpoint
A URL owned by one of your customers (or your own system) that should receive events. Each endpoint has:
- A secret (
whsec_…) used to sign payloads - A list of event subscriptions (e.g.
order.*,user.created) - An optional IP allowlist — deliveries are skipped for events whose source IP is not on the list
- An active flag — inactive endpoints receive no deliveries
Event
An event is what you publish via the Ingest API. It has a event_type string and an arbitrary JSON payload. HookSync stores the event and fans it out to all matching endpoints.
Delivery
One delivery = one attempt to POST an event to one endpoint. If the first attempt fails, HookSync retries automatically (see Retry policy). Each delivery records the HTTP status code, response body (up to 8 KB), and any network error.
Consumer
An optional entity representing one of your end-customers. Linking events and endpoints to a consumer lets you scope the Consumer portal so each customer sees only their own data.
Authentication
API keys (M2M)
For server-to-server calls use a Bearer token in the Authorization header:
Authorization: Bearer sk_0a1b2c3d... # 64 hex chars
API keys are stored as a SHA-256 hash — HookSync cannot recover the raw value. If a key is compromised, rotate it in Settings → API keys; the old key is invalidated immediately.
Session auth (dashboard)
The web dashboard uses cookie-based session auth (sid=, HttpOnly, Secure, SameSite=Strict). Mutating requests from the dashboard require a CSRF token delivered via the X-CSRF-Token header. You do not need to handle this manually — it is only relevant if you are building a custom frontend on top of the session API.
Creating an API key
POST /api/v1/organizations/{org_id}/api-keys
Authorization: Bearer sk_...
Content-Type: application/json
{
"name": "production-ingest",
"expires_at": "2027-01-01T00:00:00Z" // optional
}
# Response — raw key returned only once:
{
"data": {
"id": "key_...",
"name": "production-ingest",
"key": "sk_0a1b2c3d...", ← store this
"created_at": "2026-05-28T10:00:00Z"
}
}Revoking an API key
DELETE /api/v1/organizations/{org_id}/api-keys/{key_id}
Authorization: Bearer sk_...
# 204 No ContentSend an event
The Ingest endpoint is the only entry point for publishing events. Your backend sends one request to HookSync; HookSync fans it out to all matching endpoints.
POST /ingest/{org_id}
Authorization: Bearer sk_...
Content-Type: application/json
Idempotency-Key: <unique-string> // optional
{
"event_type": "order.placed",
"payload": { "order_id": "ord_123", "amount": 9900 },
"consumer_uid": "user_456" // optional — scope to one consumer
}Request fields
| Field | Type | Required | Description |
|---|---|---|---|
| event_type | string | yes | Dot-separated type identifier. Max 128 chars. Examples: order.placed, invoice.paid. |
| payload | object | yes | Arbitrary JSON. Delivered verbatim to endpoints. |
| consumer_uid | string | no | Routes the event only to endpoints belonging to this consumer. |
Idempotency
Add an Idempotency-Key header to make the call safe to retry. If HookSync has already processed a request with the same key within 24 hours it returns the original response without creating a duplicate event.
Idempotency-Key: order_placed_ord_123_20260528
Response
HTTP/1.1 201 Created
{
"data": {
"event_id": "evt_a1b2c3...",
"dispatched": 3 // endpoints that received delivery jobs
}
}201 response guarantees the event was persisted and delivery jobs were enqueued. It does not mean delivery succeeded — use the delivery logs to track actual HTTP responses.Batch ingest
Send up to 100 events in a single HTTP request. Useful for high-throughput pipelines where you want to reduce connection overhead.
POST /ingest/{org_id}/batch
Authorization: Bearer sk_...
Content-Type: application/json
[
{
"event_type": "order.placed",
"payload": { "order_id": "ord_001" }
},
{
"event_type": "payment.captured",
"payload": { "order_id": "ord_001", "amount": 9900 }
}
]Response
HTTP/1.1 201 Created
{
"data": {
"accepted": 2,
"event_ids": ["evt_...", "evt_..."]
}
}Limits
| Limit | Value |
|---|---|
| Max events per batch | 100 |
| Max payload size per event | 256 KB |
| Ingest rate (FREE) | 10 req/s |
| Ingest rate (PRO) | 1 000 req/s |
Event types & filtering
Every endpoint subscribes to one or more event type patterns. When an event is ingested, HookSync evaluates each active endpoint against the event type and delivers only to matching ones.
Exact match
"events": ["order.placed"] # matches: order.placed # does not match: order.refunded, order.placed.v2
Wildcard suffix
"events": ["order.*"] # matches: order.placed, order.refunded, order.cancelled # does not match: payment.captured, order.placed.v2
Catch-all
"events": ["*"] # matches every event type
Multiple patterns
"events": ["order.*", "payment.captured", "user.created"] # endpoint receives events matching ANY of the listed patterns
order.* matches order.placed but not order.placed.v2. Use * (catch-all) when you need everything regardless of nesting.Updating subscriptions
PATCH /api/v1/organizations/{org_id}/webhooks/{webhook_id}
Authorization: Bearer sk_...
Content-Type: application/json
{
"events": ["order.*", "invoice.paid"]
}Retry policy
HookSync considers a delivery successful when the endpoint returns any 2xx HTTP status within 30 seconds. Any other status code — or a network error / timeout — is treated as a failure and triggers the retry schedule.
| Attempt | Delay after previous | Cumulative elapsed |
|---|---|---|
| 1 (initial) | — | 0 |
| 2 | 5 minutes | 5 min |
| 3 | 30 minutes | 35 min |
| 4 | 2 hours | 2 h 35 min |
| 5 | 5 hours | 7 h 35 min |
| 6 | 10 hours | 17 h 35 min |
After attempt 6 the delivery is marked Abandoned. The organization owner receives an email notification with the endpoint URL, event type, delivery ID, and number of attempts made.
Manual retry
Once you have fixed the issue on your server you can trigger an immediate retry without waiting for the next scheduled attempt:
POST /api/v1/organizations/{org_id}/webhooks/{webhook_id}/deliveries/{delivery_id}/retry
Authorization: Bearer sk_...
# 204 No Content — delivery is re-queued immediatelyFailure rate alerts
HookSync checks the failure rate for every endpoint every 6 hours. If more than 50% of deliveries in the past 7 days failed, the organization owner receives an email alert so you can investigate before the endpoint is abandoned entirely.
Payload signing
Every delivery request carries an X-Webhook-Signatureheader. The value is an HMAC-SHA256 of the raw request body, computed with the endpoint's unique secret.
X-Webhook-Signature: sha256=3d4f5e6a7b8c...
How it works
Before each delivery attempt HookSync:
- Serializes the event payload to JSON
- Computes
HMAC-SHA256(secret, raw_body) - Hex-encodes the digest and prefixes it with
sha256= - Attaches it as
X-Webhook-Signaturein the request headers
Why this matters
Verifying the signature on your server proves two things:
- Authenticity — the request came from HookSync (holds the secret), not a third party
- Integrity — the payload was not modified in transit
crypto.timingSafeEqual in Node.js, hmac.compare_digest in Python) when comparing the expected and received signatures. A naive string comparison leaks timing information and can be exploited.Endpoint secret rotation
If a secret is compromised, update the endpoint to generate a new one — all future deliveries will use the new secret. Deliveries already queued with the old secret will complete with the old signature.
Verifying signatures
Copy the snippet for your server language. In every case the logic is identical: recompute the HMAC over the raw request body and compare with the header value using a timing-safe function.
Delivery logs
Every delivery attempt is stored permanently with full request and response data. Your customers can self-serve 90% of integration issues without contacting your support team.
What is logged per attempt
| Field | Description |
|---|---|
| status | pending · delivered · failed · abandoned |
| last_response_code | HTTP status code returned by the endpoint (e.g. 200, 500) |
| response_body | First 8 KB of the response body — useful for debugging server-side errors |
| last_error | Network-level error description when the request could not be made (DNS failure, timeout, TLS error) |
| attempts | Number of attempts made so far |
| next_attempt_at | Timestamp of the next scheduled retry (null when delivered or abandoned) |
| delivered_at | Timestamp of successful delivery |
Listing deliveries
GET /api/v1/organizations/{org_id}/webhooks/{webhook_id}/deliveries?page=1&per_page=50
Authorization: Bearer sk_...
# Response:
{
"data": [
{
"id": "del_...",
"event_type": "order.placed",
"status": "delivered",
"attempts": 1,
"last_response_code": 200,
"response_body": "ok",
"delivered_at": "2026-05-28T10:05:01Z"
},
{
"id": "del_...",
"event_type": "order.refunded",
"status": "failed",
"attempts": 3,
"last_response_code": 500,
"response_body": "Internal Server Error: database connection refused",
"next_attempt_at": "2026-05-28T12:05:01Z"
}
],
"meta": { "page": 1, "per_page": 50, "total": 124 }
}Consumer portal — overview
The consumer portal is a ready-made UI you can expose directly to your end-customers. Each customer gets a scoped, token-protected view where they can manage their own webhook endpoints and inspect delivery history — without seeing data from other customers and without any access to your organization-level controls.
What customers can do in the portal
- Create, edit, and delete their own webhook endpoints
- Subscribe to specific event types using wildcard patterns
- Browse events and delivery history scoped to their account
- Inspect per-attempt details: HTTP status, response body, error message
What customers cannot do
- See endpoints or events belonging to other consumers
- Access organization settings, API keys, or billing
- Perform any action beyond their own endpoint management
Generate a portal token
Before redirecting a customer to the portal, your backend generates a short-lived access token scoped to that customer. The token is tied to a consumer_uid — a stable identifier from your own system (user ID, account ID, etc.).
POST /api/v1/portal/token
Authorization: Bearer sk_...
Content-Type: application/json
{
"org_id": "{org_id}",
"consumer_uid": "user_456",
"expires_in_secs": 3600 // default: 3600 (1 hour), max: 86400
}
# Response:
{
"data": {
"token": "pt_a1b2c3d4...",
"expires_at": "2026-05-28T11:00:00Z",
"portal_url": "https://api.hooksync.net/portal/v1?token=pt_a1b2c3d4..."
}
}Security properties
- Tokens are single-use scoped to one
consumer_uid - Expired tokens are rejected immediately
- A customer with a valid token cannot access any other consumer's data
- Tokens are generated server-side — never expose your API key to the browser
Embed in your product
There are two integration patterns: redirect and iframe. Choose based on your UX requirements.
Option A — redirect (recommended)
The simplest integration. When a customer clicks “Manage webhooks”, your backend generates a token and redirects the browser to the portal URL:
# Server-side (any language) token = hooksync.portal.generate_token( consumer_uid: current_user.id, expires_in_secs: 3600 ) redirect_to token.portal_url
Option B — iframe
Embed the portal directly inside your dashboard with an <iframe>. Pass the token as a query parameter. The portal respects your CSS custom properties for basic white-labelling.
<iframe src="https://api.hooksync.net/portal/v1?token=pt_..." style="width: 100%; min-height: 600px; border: none;" title="Webhook settings" />
White-labelling
The portal reads CSS custom properties from the parent page when embedded as an iframe (same-origin) or from the query string parameters accent, bg, and font(cross-origin). This lets you match the portal to your product's design system without rebuilding it.
OpenAPI spec
The complete API schema is available as an OpenAPI 3.1 document. Import it into Postman, Insomnia, or any API client to get auto-generated request builders and response type hints.
| Resource | URL |
|---|---|
| OpenAPI JSON | GET /api-docs/openapi.json |
| Interactive Swagger UI | GET /api-docs |
# Download the spec curl https://api.hooksync.net/api-docs/openapi.json -o hooksync-openapi.json # Import into Postman # File → Import → select hooksync-openapi.json
SDK examples
Send events and manage resources from your backend using idiomatic code in your language of choice.
Endpoints reference
Authentication
| Method | Path | Description |
|---|---|---|
| POST | /api/v1/auth/register | Create account |
| POST | /api/v1/auth/login | Password login — sets sid cookie |
| POST | /api/v1/auth/logout | Invalidate session |
| POST | /api/v1/auth/mfa/verify | Complete MFA login challenge |
| POST | /api/v1/auth/password-reset/request | Send password-reset email |
| POST | /api/v1/auth/password-reset/confirm | Apply new password with reset token |
| POST | /api/v1/auth/verify-email | Verify email address with token |
Organizations & members
| Method | Path | Description |
|---|---|---|
| GET | /api/v1/organizations | List organizations for the authenticated user |
| POST | /api/v1/organizations | Create organization |
| GET | /api/v1/organizations/{org_id} | Get organization details and plan |
| DELETE | /api/v1/organizations/{org_id} | Delete organization (cascades) |
| GET | /api/v1/organizations/{org_id}/members | List members with roles |
| POST | /api/v1/organizations/{org_id}/members/invite | Invite member by email |
| DELETE | /api/v1/organizations/{org_id}/members/{member_id} | Remove member |
API keys
| Method | Path | Description |
|---|---|---|
| GET | /api/v1/organizations/{org_id}/api-keys | List API keys |
| POST | /api/v1/organizations/{org_id}/api-keys | Create API key — returns raw key once |
| DELETE | /api/v1/organizations/{org_id}/api-keys/{key_id} | Revoke API key |
Ingest
| Method | Path | Description |
|---|---|---|
| POST | /ingest/{org_id} | Publish a single event |
| POST | /ingest/{org_id}/batch | Publish up to 100 events |
Webhook endpoints
| Method | Path | Description |
|---|---|---|
| GET | /api/v1/organizations/{org_id}/webhooks | List endpoints |
| POST | /api/v1/organizations/{org_id}/webhooks | Create endpoint |
| PATCH | /api/v1/organizations/{org_id}/webhooks/{webhook_id} | Update endpoint (name, url, events, active, ip_allowlist) |
| DELETE | /api/v1/organizations/{org_id}/webhooks/{webhook_id} | Delete endpoint |
Events & deliveries
| Method | Path | Description |
|---|---|---|
| GET | /api/v1/organizations/{org_id}/events | List events (newest first) |
| GET | /api/v1/organizations/{org_id}/events/{event_id} | Get event with delivery list |
| GET | /api/v1/organizations/{org_id}/webhooks/{webhook_id}/deliveries | List deliveries for an endpoint |
| POST | /api/v1/organizations/{org_id}/webhooks/{webhook_id}/deliveries/{delivery_id}/retry | Retry a failed delivery immediately |
Consumer portal
| Method | Path | Description |
|---|---|---|
| POST | /api/v1/portal/token | Generate scoped portal access token |
| GET | /portal/v1/endpoints | Consumer: list own endpoints |
| POST | /portal/v1/endpoints | Consumer: create endpoint |
| PATCH | /portal/v1/endpoints/{webhook_id} | Consumer: update own endpoint |
| DELETE | /portal/v1/endpoints/{webhook_id} | Consumer: delete own endpoint |
| GET | /portal/v1/events | Consumer: list own events |