Guide
Destination Delivery Signatures
Source authentication proves the inbound request came from the expected sender. Destination delivery signatures prove the outbound request reaching your receiver came through FastHook.
Use destination signatures on HTTP receivers that need to reject forged delivery requests before parsing payloads or running business side effects.
FastHook signs the exact outbound body with the project's signing secret when the HTTP destination has config.auth_type set to FASTHOOK_SIGNATURE.
What The Signature Proves
Destination signatures are an outbound trust boundary. They do not replace provider source authentication, and they do not prove that the provider originally signed the request. They prove that this receiver request was produced by FastHook for a routed event branch.
Keep the two secrets separate: provider HMAC or API key material belongs in source config, while the FastHook project signing secret belongs in the receivers that verify outbound delivery.
- Verify inbound provider traffic at the source when the provider supports BASIC_AUTH, API_KEY, or HMAC.
- Verify outbound FastHook delivery at the receiver when the destination has auth_type FASTHOOK_SIGNATURE.
- Use x-fasthook-event-id and x-fasthook-request-id for idempotency and debugging, not as the signature secret.
- A valid FastHook delivery signature does not make the downstream operation idempotent; your receiver still needs duplicate protection.
Enable Signed HTTP Delivery
The backend accepts FASTHOOK_SIGNATURE on HTTP destinations. The auth object is empty because the signing material comes from the team's Project Secrets, not from per-destination inline credentials.
The current dashboard destination editor manages the destination URL, HTTP method, enabled state, description, and rate limit. Use the public API or automation when you need to create, patch, or audit destination auth_type.
- FASTHOOK_SIGNATURE is for HTTP destinations.
- Use auth: {} with FASTHOOK_SIGNATURE.
- Keep any existing url, http_method, rate_limit, and rate_limit_period fields when replacing a destination with PUT.
- Use PATCH when you only need to set auth_type without replacing the full config.
- CLI destinations are local tunnel deliveries and do not use these destination signature headers.
curl -X PATCH "https://api.fasthook.io/v1/destinations/des_xxx" \
-H "Authorization: Bearer fhp_xxx" \
-H "x-team-id: tm_xxx" \
-H "Content-Type: application/json" \
-d '{
"config": {
"auth_type": "FASTHOOK_SIGNATURE",
"auth": {}
}
}'Headers FastHook Sends
For signed HTTP delivery, FastHook sends a Unix timestamp header and an HMAC-SHA256 signature. The signature value uses the v1= prefix. The signature base string is the timestamp, a dot, and the exact outbound body delivered to the receiver.
This was checked against the live demo backend by routing a signed destination to an echo endpoint: auth_type FASTHOOK_SIGNATURE was accepted, the attempt succeeded, the timestamp and signature headers were present, and the signature verified with signing_secret.value over timestamp.rawBody.
- x-fasthook-timestamp: Unix timestamp in seconds.
- x-fasthook-signature: v1=<hmac-sha256-hex>.
- x-fasthook-event-id: the routed event branch that created this attempt.
- x-fasthook-request-id: the original accepted source request.
- Verify the raw outbound body before parsing JSON or form data.
x-fasthook-timestamp: 1772131200
x-fasthook-signature: v1=0f4d...
x-fasthook-event-id: evt_xxx
x-fasthook-request-id: req_xxx
signing input:
1772131200.<raw outbound request body>Verify At The Receiver
Read the raw request body first. Middleware that parses JSON and then re-serializes it can change whitespace, key order, encoding, or line endings, which breaks the digest. Verify the signature, enforce a timestamp tolerance, then parse and process the payload.
If a transformation rule changes the event before delivery, verify the transformed outbound body that reached your receiver, not the original provider body stored on the request.
- Use a constant-time comparison after checking both strings have the same length.
- Return 401 or 403 on missing, stale, malformed, or mismatched signatures.
- Do not log the signing secret or full signature value.
- Store x-fasthook-event-id with your internal job id so event retry can be traced later.
import { createHmac, timingSafeEqual } from "node:crypto";
function safeEqual(left, right) {
const a = Buffer.from(left);
const b = Buffer.from(right);
if (a.length !== b.length) return false;
return timingSafeEqual(a, b);
}
export function verifyFastHookDelivery({
rawBody,
timestamp,
signature,
signingSecret,
toleranceSeconds = 300
}) {
if (!signingSecret) return false;
if (!timestamp || !/^\d+$/.test(timestamp)) return false;
if (!signature || !signature.startsWith("v1=")) return false;
const timestampSeconds = Number(timestamp);
const nowSeconds = Math.floor(Date.now() / 1000);
if (!Number.isSafeInteger(timestampSeconds)) return false;
if (Math.abs(nowSeconds - timestampSeconds) > toleranceSeconds) return false;
const digest = createHmac("sha256", signingSecret)
.update(timestamp + "." + rawBody)
.digest("hex");
return safeEqual("v1=" + digest, signature);
}Use The Project Signing Secret
Project Secrets contains two different values. api_key.value authenticates Admin API automation. signing_secret.value verifies outbound FastHook delivery signatures in your receivers.
GET /v1/project-secrets returns full values, so treat the response as sensitive. Rotate signing_secret after exposure and update every receiver that verifies FastHook deliveries before retrying failed events.
- api_key.value is for API and CLI automation.
- signing_secret.value is for receiver-side delivery verification.
- Source HMAC secrets are provider-specific source auth secrets.
- Do not reuse provider production source secrets as FastHook destination signing secrets.
- After rotating signing_secret, old signatures will fail unless your receiver deliberately accepts the previous value during a migration window.
curl "https://api.fasthook.io/v1/project-secrets" \
-H "Authorization: Bearer fhp_xxx" \
-H "x-team-id: tm_xxx"
curl -X POST "https://api.fasthook.io/v1/project-secrets/rotate" \
-H "Authorization: Bearer fhp_xxx" \
-H "x-team-id: tm_xxx" \
-H "Content-Type: application/json" \
-d '{ "rotate": ["signing_secret"] }'Debug Signature Failures
A destination signature failure usually appears as a failed delivery attempt with a 401 or 403 response from your receiver. Inspect the Attempt body and receiver logs before replaying. If the receiver is still rejecting signatures, retries only create more failed attempts.
- No FastHook signature headers: destination auth_type is not FASTHOOK_SIGNATURE, the destination is not HTTP, or the attempt came from a different destination.
- Signature mismatch: wrong signing_secret, parsed body instead of raw body, transformed body mismatch, or middleware changed the payload.
- Timestamp outside tolerance: receiver clock skew, an overly strict tolerance, or a replayed request outside your accepted window.
- Valid signature but duplicate processing: add receiver idempotency using x-fasthook-event-id, x-fasthook-request-id, or the provider event id.
- Receiver fixed: retry one failed event before starting a broad bulk retry.
curl "https://api.fasthook.io/v1/attempts?event_id=evt_xxx&order_by=created_at&dir=desc&limit=20" \
-H "Authorization: Bearer fhp_xxx" \
-H "x-team-id: tm_xxx"
curl -X POST "https://api.fasthook.io/v1/events/evt_xxx/retry" \
-H "Authorization: Bearer fhp_xxx" \
-H "x-team-id: tm_xxx"Production Checklist
- Enable FASTHOOK_SIGNATURE on every HTTP destination whose receiver trusts only FastHook delivery.
- Keep source auth and destination signature verification documented separately.
- Read and verify the raw body before JSON parsing.
- Enforce a timestamp tolerance such as five minutes.
- Use timing-safe comparison for the v1 signature.
- Store event and request ids with downstream side effects.
- Rotate signing_secret immediately after exposure.
- Retry only after the receiver accepts a fresh signed test delivery.