Guide to Fiber AI Webhooks: Features and Best Practices
Fiber AI is a B2B data API for finding and enriching people and companies. Many of its operations run in the background, including batch enrichments, lookups, searches, audience builds and Tracker monitoring, so webhooks are how your system learns that a job has finished, whether it succeeded, and where to find the results.
This guide covers how Fiber AI webhooks are configured and signed, the events and their payloads, the retry schedule, and the practices that keep a Fiber AI receiver reliable.
What are Fiber AI webhooks?
Fiber AI delivers webhooks through Svix. You add an HTTPS endpoint, pick the event types it should receive, and Fiber AI posts a JSON body to it when one of those events happens. Svix handles the delivery itself: retries with backoff, signatures, a self-service portal with delivery logs, and one-click replay.
Every delivery carries svix-id, svix-timestamp and svix-signature headers. The signature is an HMAC-SHA256 of the ID, the timestamp and the raw body, keyed with the endpoint's whsec_... signing secret. This is the same construction as Standard Webhooks, with svix- header names.
Fiber AI webhook features
| Feature | Details |
|---|---|
| Delivery platform | Svix |
| Configuration | Portal via fiber.ai/app/webhooks, or the webhook endpoints API |
| Endpoints | HTTPS only, subscribed to at least one event type |
| Payload | Varies by event; most completion events carry success, an identifier and error |
| Signature headers | svix-id, svix-timestamp, svix-signature |
| Scheme | v1,<base64 HMAC-SHA256>, space-separated entries |
| Signed content | {svix-id}.{svix-timestamp}.{raw body} |
| Secret | whsec_..., per endpoint, shown only at create and rotate |
| Rotation | The old secret keeps verifying for a short grace period |
| Timeout | 15 seconds; 3xx counts as a failure |
| Retries | Immediately, then 5s, 5min, 30min, 2h, 5h, 10h, 10h |
| Ordering | Not guaranteed |
| Source IPs | A published set, available on request |
Common events
Fiber AI's catalogue has 26 event types. The docs group 25 of them, and the endpoint API also accepts batch_.
| Area | Events | Fires when |
|---|---|---|
| Enrichment | reveal.completed, batch_, batch_, mosaic.completed, audience.enrichment_, audience.prospect_, audience.company_ | A contact reveal, enrichment, file repair or audience export finishes |
| Lookup | github_, github_lookup.completed, domain_lookup.completed, social_ | A lookup job finishes |
| Search | saved_, saved_, combined_, google_, local_, audience.build_completed | A saved search is about a day away or has run, or an async search or audience build finishes |
| Monitoring | tracker.signal_detected, tracker.list_, tracker.list_, job.changed, job_ | A Tracker rule fires, a tracker list refresh is due or done, or tracked people change jobs |
| Other | depth_chart.completed, sales_, sales_ | A depth chart or Sales Navigator scrape finishes |
The tracker list run events are turned on per organisation. Subscribing an endpoint isn't enough; ask Fiber AI to enable them.
Here is Fiber AI's reveal.completed example:
{
"task_id": "abc123",
"success": true,
"emails": [{ "emailAddress": "jane@company.com", "type": "work" }],
"phone_numbers": [{ "phoneNumber": "+1-555-0100", "type": "mobile" }],
"linkedin_url": "https://www.linkedin.com/in/jane-doe"
}
successsays whether the job worked, anderroris filled in when it didn't.- The identifier depends on the operation:
task_id,run_id,search_idoraudience_id. job.changedis different. It carries aprofilesarray with nosuccessorerror, and each profile includes thelistIdit was tracked on, so one person can arrive once per list.batch_reports counts (live_ enrich.completed completed,failed,not_found,malformed) instead oferror.- The heads-up events (
saved_,search.run_ upcoming tracker.list_,run_ upcoming tracker.list_) only ever sendrun_ completed success: true. - Tracker signals use a richer envelope with the matched entity, the rule, a stable
eventIdandoccurredAt.
The documented example has no field naming the event type, and Fiber AI documents no event header. Call listWebhookEventTypes for each type's examplePayload, or send yourself test events, before deciding how to route.
Setting up Fiber AI webhooks
- Go to
fiber.ai/app/webhooksand click Set up webhooks. Always enter the portal from there, because portal links are short-lived. - Click Add Endpoint and enter your HTTPS URL.
- Select the event types to receive and save. Delivery starts immediately.
- Copy the endpoint's signing secret (
whsec_...) and store it server-side, for example asFIBER_AI_WEBHOOK_SECRET.
You can do the same over the API, free of credits:
curl -X POST "https://api.fiber.ai/v1/webhooks/endpoints" \
-H "Content-Type: application/json" \
-d '{"apiKey": "'"$FIBER_API_KEY"'",
"url": "https://example.com/webhooks/fiber",
"eventTypes": ["reveal.completed", "batch_contact_enrich.completed"]}'
The response's output.signingSecret is only returned here and by rotateWebhookEndpointSecret. Reads never return it.
If you receive Fiber AI webhooks through Hookdeck, use the Hookdeck source URL as the endpoint and paste that endpoint's whsec_... secret into the Fiber AI source. Each endpoint has its own secret, so if you create several endpoints, give each one its own Hookdeck source.
To test, use sendTestWebhookEvent or the portal's Send Example button. Both deliver an example payload of the type you choose, signed like a real one.
Securing Fiber AI webhooks
Join svix-id, svix-timestamp and the raw request body with dots. Take the part of the secret after whsec_, base64-decode it, and use those bytes as the HMAC-SHA256 key. Base64-encode the result and compare it in constant time with each v1, entry in the space-separated svix-signature header. A match on any entry is enough.
Fiber AI's docs, and the Fiber AI skill's examples, use the official svix library. It always rejects an svix-timestamp more than 5 minutes from your clock, and the window can't be turned off. Hookdeck retries and replays resend the original svix-timestamp, so a delivery that reaches your code later than that fails even though its signature is valid. If you receive through Hookdeck, use a manual verifier like the one below, or the one in the skill's references/verification.md, where the window is optional and off by default.
const crypto = require('crypto');
const express = require('express');
function verifyFiberSignature(rawBody, headers, secret, toleranceSeconds = 0) {
const id = headers['svix-id'];
const timestamp = headers['svix-timestamp'];
const signatureHeader = headers['svix-signature'];
if (!id || !timestamp || !signatureHeader || !secret || !secret.startsWith('whsec_')) return false;
if (!/^\d+$/.test(timestamp)) return false;
// Optional replay check, off by default.
if (toleranceSeconds > 0 && Math.abs(Date.now() / 1000 - Number(timestamp)) > toleranceSeconds) {
return false;
}
const key = Buffer.from(secret.slice('whsec_'.length), 'base64');
const expected = crypto.createHmac('sha256', key).update(`${id}.${timestamp}.`).update(rawBody).digest();
return signatureHeader.split(' ').some((entry) => {
const [version, signature] = entry.split(',');
if (version !== 'v1' || !signature) return false;
const received = Buffer.from(signature, 'base64');
return received.length === expected.length && crypto.timingSafeEqual(received, expected);
});
}
const app = express();
app.post('/webhooks/fiber', express.raw({ type: '*/*' }), (req, res) => {
const secret = process.env.FIBER_AI_WEBHOOK_SECRET;
if (!secret) return res.sendStatus(500); // fail closed
if (!verifyFiberSignature(req.body, req.headers, secret)) {
return res.sendStatus(401);
}
const event = JSON.parse(req.body.toString('utf8'));
// Deduplicate on req.headers['svix-id'], then process asynchronously.
res.sendStatus(200);
});
The same check in Python:
import base64
import binascii
import hashlib
import hmac
import time
def verify_fiber_signature(raw_body: bytes, headers, secret: str, tolerance_seconds: int = 0) -> bool:
msg_id = headers.get("svix-id")
timestamp = headers.get("svix-timestamp")
signature_header = headers.get("svix-signature")
if not (msg_id and timestamp and signature_header and secret and secret.startswith("whsec_")):
return False
if not timestamp.isdigit():
return False
# Optional replay check, off by default.
if tolerance_seconds > 0 and abs(time.time() - int(timestamp)) > tolerance_seconds:
return False
try:
key = base64.b64decode(secret[len("whsec_"):])
except (binascii.Error, ValueError):
return False
signed = f"{msg_id}.{timestamp}.".encode("utf-8") + raw_body
expected = base64.b64encode(hmac.new(key, signed, hashlib.sha256).digest())
for entry in signature_header.split(" "):
version, _, signature = entry.partition(",")
if version == "v1" and hmac.compare_digest(signature.encode("utf-8"), expected):
return True
return False
# In FastAPI: raw_body = await request.body()
# verify_fiber_signature(raw_body, request.headers, os.environ["FIBER_AI_WEBHOOK_SECRET"])
To check your verifier, use the secret whsec_, the ID msg_, the timestamp 1761400000 and this exact body:
{"task_id":"abc123","success":true,"linkedin_url":"https://www.linkedin.com/in/jane-doe"}
The svix-signature value should be:
v1,b8/3JsNDYwrxzxBJuq2ImhIzv8SPtBNv03e8LsNMjoQ=
Make Fiber AI webhooks production-ready. Hookdeck Event Gateway verifies the Svix signature on every Fiber AI delivery, answers well inside the 15-second timeout, and holds each job result until your service has processed it.
Fiber AI webhook limitations and pain points
The body doesn't name the event
The Problem: One handler receives several event types and can't tell which one arrived.
Why It Happens: Fiber AI documents no event-type header, and its documented example body has no type field. Payload shapes also overlap: most completion events carry success, an identifier and error.
Workarounds:
- Check
examplePayloadfromlistWebhookEventTypes, or a test delivery, for each type you subscribe to. - Create a separate endpoint for each event type, or each group you handle the same way, with its own URL path, so the route tells you the type.
How Hookdeck Can Help: Give each Fiber AI endpoint its own Hookdeck source and route each one to the right handler. Hookdeck filters can also route on fields such as profiles or task_id when one source receives several types.
The library's fixed 5-minute window
The Problem: Genuine events fail verification when they are retried or replayed late.
Why It Happens: The svix libraries reject any svix-timestamp more than 5 minutes from the current time, and a retry or replay from Hookdeck keeps the original timestamp because it is part of the signed content.
Workarounds:
- Verify manually and choose your own window, or drop the window and deduplicate on
svix-id. - Run verification as soon as the request arrives, before it is queued.
How Hookdeck Can Help: Hookdeck verifies the signature when the request arrives and applies no timestamp window, so retrying or replaying an event from Hookdeck doesn't fail verification at the source.
Out-of-order and repeated deliveries
The Problem: A later result overwrites a newer one, or the same job is processed twice.
Why It Happens: Fiber AI doesn't guarantee order, and retries over roughly a day mean the same message can arrive more than once. People tracked on several job-change lists arrive once per list.
Workarounds:
- Deduplicate on
svix-id, which stays the same across retries. Tracker signals also carry a stableeventId. - Order Tracker signals by
occurredAt, not by arrival. - Treat
job.changedentries perlistId.
How Hookdeck Can Help: Hookdeck's deduplication can drop repeated deliveries on svix-id, and its delivery queue smooths bursts from large batch jobs.
Secrets shown only twice
The Problem: A lost secret means you can't verify deliveries until you rotate it.
Why It Happens: Fiber AI returns the signing secret only when an endpoint is created or its secret is rotated.
Workarounds:
- Save the secret into your secrets manager in the same step that creates the endpoint.
- To recover, rotate the secret and deploy the new one during the grace period, while the old one still verifies.
How Hookdeck Can Help: With Hookdeck in front, only the Fiber AI source holds the secret, so a rotation means updating one field.
Best practices
Verify against the raw body
The signature covers the exact bytes. Use express.raw({ type: '*/*' }) in Express, await request.text() in a Next.js route handler and await request.body() in FastAPI, and parse only after verification.
Check success before using results
A completion event can report a failed job. Branch on success, log error, and only fetch or store results for jobs that worked.
Pause endpoints during maintenance
Update an endpoint with disabled: true to stop deliveries without deleting it, and set it back to false to resume. A message that exhausts its retries is marked failed in the portal, where you can retry it manually.
Acknowledge fast and process asynchronously
Verify, store, return 200, and do the work afterwards. Slow handlers hit the 15-second timeout and cause needless retries. See why to process webhooks asynchronously and our guide to webhook idempotency.
Conclusion
Fiber AI webhooks arrive through Svix, signed with the svix-* headers and a per-endpoint whsec_ secret. Save the secret when you create the endpoint, verify against the raw body with a verifier whose window suits your retry path, work out how you will tell event types apart, and deduplicate on svix-id.
Hookdeck Event Gateway sits in front of your receiver, checks every Fiber AI signature, and holds each event until your service has processed it.
Get started with Hookdeck for free and handle Fiber AI webhooks reliably in minutes.