# Fiber AI Webhooks

Fiber AI (B2B data and enrichment) runs many operations in the background and tells
your system when a job finishes by sending a webhook to your HTTPS endpoint.
Deliveries are sent through Svix, so verification is the standard Svix scheme.

## When to Use This Skill

* How do I receive Fiber AI webhooks?
* How do I verify Fiber AI webhook signatures (`svix-signature`)?
* Why is my Fiber AI webhook signature verification failing?
* How do I handle `reveal.completed`, `batch_contact_enrich.completed` or `tracker.signal_detected`?
* Why are Hookdeck retries or replays of Fiber AI webhooks rejected?

## Verification (core)

Every delivery carries `svix-id`, `svix-timestamp` and `svix-signature`. The signature
is base64 `HMAC-SHA256` over `{svix-id}.{svix-timestamp}.{raw body}`, keyed with the
base64-decoded part of the `whsec_` secret after the prefix. `svix-signature` may hold
several space-separated `v1,<base64>` entries (secret rotation); any match is valid.
Use the official `svix` library and pass the **raw body**:

```javascript
import { Webhook } from 'svix';

const wh = new Webhook(process.env.FIBER_AI_WEBHOOK_SECRET); // "whsec_..."
wh.verify(rawBody, {                                          // raw Buffer/string, not parsed JSON
  'svix-id': req.headers['svix-id'],
  'svix-timestamp': req.headers['svix-timestamp'],
  'svix-signature': req.headers['svix-signature'],
});                                                           // throws on a bad signature or old timestamp
const event = JSON.parse(rawBody.toString('utf8'));           // parse only after verifying

```

```python
from svix.webhooks import Webhook, WebhookVerificationError

Webhook(os.environ["FIBER_AI_WEBHOOK_SECRET"]).verify(raw_body, {
    "svix-id": headers["svix-id"],
    "svix-timestamp": headers["svix-timestamp"],
    "svix-signature": headers["svix-signature"],
})  # raises WebhookVerificationError

```

> **For complete handlers with tests**, see [examples/express/](https://github.com/hookdeck/webhook-skills/tree/main/skills/fiber-ai-webhooks/examples/express/), [examples/nextjs/](https://github.com/hookdeck/webhook-skills/tree/main/skills/fiber-ai-webhooks/examples/nextjs/), [examples/fastapi/](https://github.com/hookdeck/webhook-skills/tree/main/skills/fiber-ai-webhooks/examples/fastapi/).
> In Express, read the body with `express.raw({ type: '*/*' })` so any content type verifies.

### Hookdeck retries and replays vs the 5-minute window

The `svix` (and `standardwebhooks`) libraries **always** enforce a fixed 5-minute
timestamp window and cannot turn it off. Hookdeck retries and replays resend the
**original** request, including the original `svix-timestamp`, so a retry or replay that
happens more than 5 minutes after Fiber AI first sent the event is rejected by the library
(`Message timestamp too old`). If your handler sits behind Hookdeck retries or manual
replays, use the manual verifier in [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/fiber-ai-webhooks/references/verification.md),
where the window can be dropped or widened. Dedupe on `svix-id` instead, since it is stable across retries.

## Common Event Types

Names are from the [Fiber AI webhook docs](https://docs.fiber.ai/monitoring/webhooks).
**Fiber AI documents no event-type field in the body and no event header**, so there is no
documented field to dispatch on. Either give each event type its own endpoint (and route by
URL), or call `listWebhookEventTypes` and match deliveries against each type's
`examplePayload`.

| Event | Fires when |
| --- | --- |
| `reveal.completed` | A contact reveal finishes |
| `batch_live_enrich.completed` | A batch live enrichment job finishes (per-outcome counts) |
| `batch_contact_enrich.completed` | A batch contact enrichment job finishes |
| `mosaic.completed` | A file repair job finishes |
| `audience.enrichment_completed` | An audience enrichment finishes |
| `tracker.signal_detected` | A Tracker rule fires a signal (`eventId`, `occurredAt`) |
| `job.changed` | Tracked profiles changed jobs (`profiles` array, no `success`/`error`) |

More: `audience.prospect_export_completed`, `audience.company_export_completed`,
`audience.build_completed`, `github_to_linkedin.completed`, `domain_lookup.completed`,
`saved_search.run_completed`, `depth_chart.completed` and others; see
[references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/fiber-ai-webhooks/references/overview.md). The examples read a `type` field only
defensively, in case one is present, and otherwise recognise `job.changed` by its `profiles`
array; keep a `default` branch and confirm what your endpoint receives with
`sendTestWebhookEvent`.

## Environment Variables

```bash
# Signing secret (starts with whsec_), shown when you create or rotate the endpoint
FIBER_AI_WEBHOOK_SECRET=whsec_xxxxx

```

## Local Development

```bash
npx hookdeck-cli listen 3000 fiber-ai --path /webhooks/fiber-ai

```

## Reference Materials

* [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/fiber-ai-webhooks/references/overview.md) - Fiber AI webhooks, events, payload shape, retries
* [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/fiber-ai-webhooks/references/setup.md) - Create an endpoint and get the signing secret
* [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/fiber-ai-webhooks/references/verification.md) - Signature details and a manual verifier without the 5-minute window