Guide
Outbound Webhooks
Outbound webhooks do not require a second FastHook data model. The same Source -> Connection -> Destination -> Event -> Attempt pipeline can be used when your own application is the producer and customer or internal endpoints are the receivers.
In this mode, your application publishes a message to a FastHook source URL. FastHook records the request, applies connection filters and transformations, signs the outbound HTTP delivery when configured, and stores every destination attempt for retry and debugging.
This guide explains the production pattern without changing existing inbound behavior. Provider webhooks, request bins, Telegram flows, S3/R2 destinations, alerts, replay, and traces continue to use the same resources.
How Outbound Mode Maps To FastHook
Webhook sending platforms often use terms like application, consumer, endpoint, subscription, message, and delivery. FastHook already has the operational equivalents; the difference is how you name and organize the resources.
Do not think of this as pretending to be another service. FastHook is the delivery layer between your publisher and the endpoints that should receive signed, retried, inspectable webhook deliveries.
- Application or producer: the service that POSTs to a FastHook source URL.
- Publish endpoint: a FastHook source with POST enabled and source auth configured.
- Consumer endpoint: an HTTP destination that points at the receiver URL.
- Subscription: a connection from the publish source to one destination, usually with filters on event type, tenant, or customer id.
- Message: the accepted request plus the event branch created for a matching connection.
- Delivery attempt: the attempt row that records destination URL, response status, response body, latency, retry trigger, and error details.
Your app
-> POST source URL
-> request req_xxx
-> connection customer-acme-invoice-events
-> event evt_xxx
-> signed HTTP destination delivery
-> attempt att_xxxWhat This Adds Beyond A Signature
A signature only proves who created one HTTP request. Outbound webhook mode is useful because the signed request is attached to routing, replay, retry, filtering, transformations, and inspection.
If a receiver is down, FastHook can retry the event branch. If the payload contract is wrong, you can inspect the request, event data, transformed payload, and failed attempt before replaying safely.
- Branch-specific routing to one or many destinations.
- Filters for event type, tenant, customer id, header, path, or query value.
- Transformations before delivery so each receiver can have its own contract.
- Destination authentication, including FASTHOOK_SIGNATURE for HTTP receivers.
- Retry rules, pause and resume, rate limits, replay, request trace, alerts, and attempt history.
- Non-HTTP destinations for operations, including Slack, Telegram, email, Google Sheets, Cloudflare R2, AWS S3, SMS, and WhatsApp.
1. Create A Publish Source
Create a normal WEBHOOK source for the application that will publish outbound events. For internal publishers, narrow allowed_http_methods to POST and enable API_KEY or HMAC source auth.
The generated source URL is the publish endpoint. Keep it stable and treat the source auth credential as a producer secret.
- Use one publish source per producer and environment, such as billing-prod-publish and billing-staging-publish.
- Use API_KEY when the producer can add a static header.
- Use HMAC when the producer can sign the raw body.
- Keep provider sources and application publish sources separate so auth, metrics, and recovery stay scoped.
curl -X POST "https://api.fasthook.io/v1/sources" \
-H "Authorization: Bearer $FASTHOOK_API_KEY" \
-H "x-team-id: $FASTHOOK_TEAM_ID" \
-H "Content-Type: application/json" \
-d '{
"name": "billing-outbound-publish",
"description": "Application publish endpoint for invoice and subscription webhooks",
"type": "WEBHOOK",
"config": {
"auth_type": "API_KEY",
"auth": {
"header_name": "x-publish-key",
"value": "replace-with-a-long-random-secret"
},
"allowed_http_methods": ["POST"]
}
}'2. Create A Signed HTTP Destination
Each receiver endpoint should be an HTTP destination. Set config.auth_type to FASTHOOK_SIGNATURE when the receiver should verify that the delivery came through FastHook.
FASTHOOK_SIGNATURE uses the project signing secret, so the destination auth object is empty. The receiver verifies x-fasthook-timestamp and x-fasthook-signature over the exact outbound body.
- Use one destination per unique receiver URL and credential boundary.
- Use destination rate limits when a customer endpoint has a known capacity limit.
- Use CUSTOM_HEADER when the receiver expects a static token instead of a FastHook signature.
- Use the destination delivery signatures guide for receiver verification code.
curl -X POST "https://api.fasthook.io/v1/destinations" \
-H "Authorization: Bearer $FASTHOOK_API_KEY" \
-H "x-team-id: $FASTHOOK_TEAM_ID" \
-H "Content-Type: application/json" \
-d '{
"name": "customer-acme-webhook-endpoint",
"description": "ACME production webhook receiver",
"type": "HTTP",
"config": {
"url": "https://customer.example.com/webhooks/fasthook",
"http_method": "POST",
"auth_type": "FASTHOOK_SIGNATURE",
"auth": {},
"rate_limit": 60,
"rate_limit_period": "minute"
}
}'3. Connect Event Types To Endpoints
A connection is the subscription. It decides which published messages should be delivered to one destination branch.
For outbound webhook workloads, filters usually match body.type, body.consumer_id, body.tenant_id, or headers that identify the receiving product or environment.
- Create one connection per endpoint when each receiver needs separate filters, retry rules, pause state, or transformations.
- Create several connections from the same publish source for fan-out.
- Pause one connection during a customer outage without stopping the producer from publishing new messages.
- Add a transformation rule when one receiver needs a different payload shape.
curl -X POST "https://api.fasthook.io/v1/connections" \
-H "Authorization: Bearer $FASTHOOK_API_KEY" \
-H "x-team-id: $FASTHOOK_TEAM_ID" \
-H "Content-Type: application/json" \
-d '{
"name": "billing-to-acme-invoices",
"source_id": "src_publish_xxx",
"destination_id": "des_customer_acme_xxx",
"rules": [
{
"type": "filter",
"body": {
"consumer_id": "cus_acme",
"type": "invoice.paid"
}
},
{
"type": "retry",
"strategy": "exponential",
"interval": 30000,
"count": 5,
"response_status_codes": ["429", "500-599"]
}
]
}'4. Publish A Message
Publishing is just a POST to the source URL. The body is your webhook message contract. Include stable ids so receivers can process idempotently and operators can search quickly.
The request is accepted at the source first. Matching connections then create destination-specific events and attempts.
- Store your message id in the body so the receiver can deduplicate.
- Use body.type as the event type routing key.
- Use body.consumer_id or body.tenant_id when different endpoints subscribe to different customers.
- Use the FastHook request id, event id, and attempt id for operational traceability.
curl -X POST "https://hook-xxxxxx.fasthook.io/" \
-H "Content-Type: application/json" \
-H "x-publish-key: replace-with-a-long-random-secret" \
-d '{
"id": "msg_invoice_paid_0001",
"type": "invoice.paid",
"consumer_id": "cus_acme",
"created_at": "2026-06-21T12:00:00Z",
"data": {
"invoice_id": "inv_123",
"customer_id": "cus_acme",
"amount": 4900,
"currency": "eur"
}
}'5. Verify Delivery At The Receiver
When a destination uses FASTHOOK_SIGNATURE, FastHook adds x-fasthook-timestamp and x-fasthook-signature. Verify the raw body before parsing JSON, then process the message.
The receiver should also store an idempotency key. Use your message id when present, or combine x-fasthook-event-id with the business id in the payload.
import { createHmac, timingSafeEqual } from "node:crypto";
function verifyFastHookDelivery(rawBody, timestamp, signature, signingSecret) {
if (!timestamp || !signature?.startsWith("v1=")) return false;
const now = Math.floor(Date.now() / 1000);
const sentAt = Number(timestamp);
if (!Number.isSafeInteger(sentAt) || Math.abs(now - sentAt) > 300) return false;
const expected = "v1=" + createHmac("sha256", signingSecret)
.update(timestamp + "." + rawBody)
.digest("hex");
const left = Buffer.from(signature);
const right = Buffer.from(expected);
return left.length === right.length && timingSafeEqual(left, right);
}6. Inspect, Retry, Replay, And Alert
Outbound publish mode uses the same evidence ladder as inbound webhook routing. Start at Requests to prove the producer published successfully, then inspect Events and Attempts to see which endpoint accepted or rejected the delivery.
Use event retry when one existing branch failed. Use request replay when you intentionally want the original published message to run through current connection rules again.
- Requests answer whether the producer reached FastHook.
- Events answer which connection branches were created or ignored.
- Attempts answer what each endpoint returned.
- Trace links connect the request, event branch, transformation boundary, and destination response.
- Alerts should be based on failed events, failed attempts, held events, and rejected requests from the same source and time window.
curl "https://api.fasthook.io/v1/requests?source_id=src_publish_xxx&status=accepted&limit=20" \
-H "Authorization: Bearer $FASTHOOK_API_KEY" \
-H "x-team-id: $FASTHOOK_TEAM_ID"
curl "https://api.fasthook.io/v1/events?source_id=src_publish_xxx&destination_id=des_customer_acme_xxx&limit=20" \
-H "Authorization: Bearer $FASTHOOK_API_KEY" \
-H "x-team-id: $FASTHOOK_TEAM_ID"
curl "https://api.fasthook.io/v1/attempts?event_id=evt_xxx&order_by=created_at&dir=desc&limit=20" \
-H "Authorization: Bearer $FASTHOOK_API_KEY" \
-H "x-team-id: $FASTHOOK_TEAM_ID"
curl -X POST "https://api.fasthook.io/v1/events/evt_xxx/retry" \
-H "Authorization: Bearer $FASTHOOK_API_KEY" \
-H "x-team-id: $FASTHOOK_TEAM_ID"Current Limits And Safe Naming
FastHook can run outbound webhook workloads today with sources, connections, HTTP destinations, signatures, retries, replay, and attempts. A dedicated customer portal, customer-managed endpoint UI, event catalog UI, and per-customer subscription product surface are separate product features, not required for the core delivery path.
Use naming conventions now so those product features can be added later without changing the delivery model.
- Name publish sources by producer and environment: billing-prod-publish.
- Name destinations by receiver: customer-acme-production-webhook.
- Name connections by subscription: billing-to-acme-invoice-paid.
- Put consumer_id, endpoint_id, event type, and message id in the payload or headers.
- Keep one destination per receiver URL so attempt history and rate limits remain clean.
- Do not mix third-party provider ingress and your own outbound publish stream in one source.
Frequently Asked Questions
- Is this a new API endpoint? No. The generated FastHook source URL is the publish endpoint.
- Does this break inbound webhooks? No. It uses the same source, connection, destination, event, and attempt pipeline.
- Can FastHook send signed outbound webhooks? Yes. Use FASTHOOK_SIGNATURE on HTTP destinations.
- Can one message fan out to many endpoints? Yes. Create multiple connections from the same publish source.
- Can I route by customer? Yes. Put consumer_id or tenant_id in the message and filter on that field in connection rules.
- Can I replay a message after changing routing? Yes. Use request replay when you want the stored source request to run through current rules.
- Can I retry one failed endpoint without touching others? Yes. Retry the failed event branch.
- Should receivers still be idempotent? Yes. Retries and replay can repeat delivery by design.
- Is this a full customer webhook portal? Not yet. It is the delivery model and API workflow; customer-facing endpoint management can be built on top.
- Should I use Svix or FastHook? Use FastHook when routing, debugging, destinations, replay, and gateway operations are central. Use a dedicated customer portal product only when that portal is the main requirement.