> ## Documentation Index
> Fetch the complete documentation index at: https://docs.shopsniffer.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhooks

Get notified when a ShopSniffer report completes — pass a webhook URL when creating a job, and optionally a notification email.

## Overview

Instead of polling `GET /api/jobs/:id/status`, pass a `webhook_url` when creating a job and ShopSniffer will POST a completion event to your URL as soon as the report is ready. You can also pass `notify_email` to receive an email notification on completion.

## Registering a webhook

Pass `webhook_url` in the body of `POST /api/jobs`:

```bash
curl -X POST https://shopsniffer.com/api/jobs \
  -H "X-API-Key: ss_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "domain": "allbirds.com",
    "webhook_url": "https://your-server.com/hooks/shopsniffer",
    "notify_email": "you@example.com"
  }'
```

Both fields are optional and independent. You can use one, the other, or both.

## Webhook payload

When the job completes, ShopSniffer POSTs this JSON body to your URL:

```json
{
  "event": "job.completed",
  "job_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "domain": "allbirds.com",
  "slug": "allbirds-com",
  "report_url": "https://shopsniffer.com/report/a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "api_url": "https://shopsniffer.com/api/reports/a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "status_url": "https://shopsniffer.com/api/jobs/a1b2c3d4-e5f6-7890-abcd-ef1234567890/status"
}
```

Content-Type is `application/json`. The only event type today is `job.completed`. Any 2xx response marks delivery as successful; anything else (including network errors) marks it as failed.

## Delivery semantics

**Single-attempt delivery — no retries**

Webhooks are delivered exactly once after the job's `notify-callbacks` workflow step runs. If your endpoint is unreachable or returns a non-2xx status, delivery is marked `failed` and **not retried automatically**. Build idempotency and a fallback poll into your integration.

- **One attempt per callback.** The workflow step runs once when the job transitions to `completed`. Status is recorded in the `webhook_callbacks` table (`pending` → `delivered` | `failed`) with an `attempts` counter.
- **No HMAC signing.** The request has no signature header. Verify the caller by one of:
  - Using a hard-to-guess secret path (e.g. `https://your-server.com/hooks/shopsniffer/<random-token>`)
  - Allowlisting the Cloudflare Workers egress IPs on your firewall
  - Cross-checking the `job_id` against a job you created and persisted locally
- **No custom headers or query params.** If you need to pass context (user ID, internal job reference, environment tag), encode it in the webhook URL path or query string when you register it.
- **Ordering.** Only one `job.completed` event per job. If the job is retried (via the admin retry endpoint), a new job ID is created with its own webhook callback.

## Fallback: poll as a safety net

Because delivery is single-attempt, pair webhooks with a lightweight status poll for mission-critical integrations:

```typescript
// Register webhook at creation
const { job } = await createJob({ domain, webhook_url });

// Also set a timer — if no webhook arrives in 10 minutes, poll once
setTimeout(async () => {
  const status = await fetch(`/api/jobs/${job.id}/status`).then(r => r.json());
  if (status.status === "completed" && !webhookReceived(job.id)) {
    handleCompletion(job.id); // reconcile manually
  }
}, 10 * 60 * 1000);
```

## Email notifications

Pass `notify_email` in the job creation body to have ShopSniffer send a completion email directly. Emails are delivered via the `EMAIL` Workers binding and link to the report URL. Tracked status: `pending` → `sent` | `failed` (recorded in the `email_notifications` table).

```bash
curl -X POST https://shopsniffer.com/api/jobs \
  -H "X-API-Key: ss_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"domain": "allbirds.com", "notify_email": "you@example.com"}'
```

Email delivery is also single-attempt — the same caveats as webhooks apply.

## Next steps

**[Jobs API](/api/jobs)**

Full `POST /api/jobs` reference including `webhook_url` and `notify_email` fields.
  **[AI agent integration](/agent-integration)**

Use webhooks to trigger autonomous agent pipelines.
  **[Monitor a store](/guides/monitor-prices)**

End-to-end guide combining tracking, webhooks, and changelog.
  **[Rate limits](/rate-limits)**

Limits for job creation and related endpoints.