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

# Webhooks

> Subscribe to Insta Swap lifecycle events with signed HTTPS POSTs.

API-enabled organizations receive **webhooks** instead of email when Insta Swaps change state.

Manage endpoints in the app (**Developer → Webhooks**) or via `POST /webhooks`.

## Events

| Event                               | When                                                            |
| ----------------------------------- | --------------------------------------------------------------- |
| `insta_swap.queued`                 | Job entered the render queue                                    |
| `insta_swap.completed`              | Render finished; result available                               |
| `insta_swap.failed`                 | Render failed                                                   |
| `digital_sample.analysis.completed` | Forensic video analysis finished; variation proposals drafted   |
| `digital_sample.analysis.failed`    | Forensic video analysis failed                                  |
| `webhook.test`                      | Verification or manual test ping (not stored as a subscription) |

<Note>
  **Webhook events ≠ Insta Swap `status`.** A swap moves through many pipeline states (`QUEUED`, `ASSIGNED`, `RENDERING`, `PENDING_APPROVAL`, `COMPLETED`, `FAILED`, `EXPIRED`, `CANCELLED`) visible on `GET /insta-swaps/{id}`. Public webhooks only fire at three integration milestones — queued, completed, and failed — so partners are not flooded with intermediate GPU/render updates. Mid-flight progress is for polling, not subscription.
</Note>

## Create an endpoint

```bash theme={null}
curl -s -X POST "$BASE_URL/webhooks" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/webhooks/yph",
    "events": ["insta_swap.completed", "insta_swap.failed"],
    "verify": true
  }'
```

When `verify` is `true` (default), YPH sends `webhook.test` before saving. Your URL must respond with **HTTP 2xx**.

On success, the response includes a **signing secret** (shown once):

```json theme={null}
{
  "endpoint": {
    "id": "…",
    "url": "https://example.com/webhooks/yph",
    "events": ["insta_swap.completed", "insta_swap.failed"],
    "secret": "…",
    "enabled": true
  }
}
```

## Payload shape

```json theme={null}
{
  "event": "insta_swap.completed",
  "idempotencyKey": "insta_swap.completed:job-uuid",
  "createdAt": "2026-06-20T12:00:00.000Z",
  "data": {
    "jobId": "…",
    "digitalSampleId": "…",
    "actorId": "…",
    "status": "COMPLETED"
  }
}
```

## Verify signatures

Each delivery includes:

| Header                  | Value          |
| ----------------------- | -------------- |
| `X-YPH-Event`           | Event name     |
| `X-YPH-Idempotency-Key` | Dedup key      |
| `X-YPH-Signature`       | `sha256=<hex>` |

Compute the signature:

```
signature = SHA256( secret + ":" + raw_request_body )
```

Compare to `X-YPH-Signature` (strip the `sha256=` prefix). Use constant-time comparison in production.

<Warning>
  Reject requests with invalid signatures. The secret is only returned at endpoint creation.
</Warning>

## List and delete

```bash theme={null}
curl -s "$BASE_URL/webhooks" -H "Authorization: Bearer YOUR_API_KEY"
curl -s -X DELETE "$BASE_URL/webhooks/ENDPOINT_ID" -H "Authorization: Bearer YOUR_API_KEY"
```

Delivery history: `GET /webhooks/{id}/deliveries`.

## Retries

Failed deliveries are retried with exponential backoff (up to 3 attempts). Check `last_error` in delivery records for debugging.
