# Argil Webhooks

[Argil](https://www.argil.ai) is an AI avatar video generation platform. Its webhooks report video renders and avatar trainings finishing or failing. Deliveries are signed per Standard Webhooks with `webhook-id`, `webhook-timestamp` and `webhook-signature` headers.

## When to Use This Skill

* How do I receive Argil webhooks?
* How do I verify an Argil webhook signature?
* Where do I get my Argil webhook signing secret?
* How do I handle `VIDEO_GENERATION_SUCCESS` or `AVATAR_TRAINING_FAILED`?
* Why is my Argil `webhook-signature` verification failing, or why are there no signature headers?
* How do I dedupe Argil webhook retries?

## Signing requirement: recreate old webhooks

Webhooks created before Argil added signing are delivered **unsigned**, and there is no way to retrieve a secret for them. Recreate the webhook to get a `whsec_` secret (Hookdeck's alert for this source says the same: "Webhooks created before Argil added signing are unsigned. Recreate the webhook to get a whsec_ secret."). The secret is returned **once**, in the response of `POST /webhooks`, so store it then.

## Verification (core)

| Header | Value |
| --- | --- |
| `webhook-id` | The event ID, the same on every retry |
| `webhook-timestamp` | Unix seconds when this attempt was sent |
| `webhook-signature` | `v1,<base64 HMAC-SHA256>`, possibly several space-separated entries |

Signed content is `{webhook-id}.{webhook-timestamp}.{raw body}`. The key is the base64-**decoded** part of the secret after `whsec_`. Argil's docs show the `standardwebhooks` library (npm, PyPI). It enforces a fixed 5-minute timestamp window and compares in constant time. If `webhook-id` is missing, read the `svix-*` headers instead: same scheme, other prefix.

```javascript
const { Webhook } = require('standardwebhooks');

// rawBody: exact request body string, NOT re-serialised JSON
function verifyArgilWebhook(rawBody, get, secret = process.env.ARGIL_WEBHOOK_SECRET) {
  const prefix = get('webhook-id') ? 'webhook' : 'svix';
  return new Webhook(secret).verify(rawBody, {
    'webhook-id': get(`${prefix}-id`),
    'webhook-timestamp': get(`${prefix}-timestamp`),
    'webhook-signature': get(`${prefix}-signature`),
  }); // returns the parsed { event, data }; throws → respond 400
}

```

Python: `from standardwebhooks.webhooks import Webhook`, then `Webhook(secret).verify(await request.body(), {...same mapping...})`.

**Late retries.** Hookdeck retries and replays resend the original timestamp, so the library rejects them after 5 minutes. A dependency-free manual verifier with no window is in [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/argil-webhooks/references/verification.md).

> **For complete handlers with tests**, see [examples/express/](https://github.com/hookdeck/webhook-skills/tree/main/skills/argil-webhooks/examples/express/), [examples/nextjs/](https://github.com/hookdeck/webhook-skills/tree/main/skills/argil-webhooks/examples/nextjs/), [examples/fastapi/](https://github.com/hookdeck/webhook-skills/tree/main/skills/argil-webhooks/examples/fastapi/).

## Event Envelope

```json
{ "event": "VIDEO_GENERATION_SUCCESS",
  "data": { "videoId": "...", "videoName": "...", "videoUrl": "https://...",
            "message": "Video generation completed successfully",
            "extras": "...", "renderCost": 150, "renderCostRefunded": false } }

```

The event name is the top-level `event` string. The payload has no event ID: dedupe on the `webhook-id` header.

## Common Event Types

| Event | Fired when | `data` fields |
| --- | --- | --- |
| `VIDEO_GENERATION_SUCCESS` | A video render succeeds | `videoId`, `videoName`, `videoUrl`, `message`, `extras`, `renderCost`, `renderCostRefunded` (`false`) |
| `VIDEO_GENERATION_FAILED` | A video render fails | `videoId`, `videoName`, `message` (why), `extras`, `renderCost`, `renderCostRefunded` |
| `AVATAR_TRAINING_SUCCESS` | An avatar training succeeds | `avatarId`, `voiceId`, `avatarName`, `extras` |
| `AVATAR_TRAINING_FAILED` | An avatar training fails | `avatarId`, `avatarName`, `extras` |

> Argil's Avatar Training Failed page calls the event `AVATAR_GENERATION_FAILED` in its prose, but its payload example and the subscription enum use `AVATAR_TRAINING_FAILED`. Use that.

> **Full payloads:** [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/argil-webhooks/references/overview.md) and [Argil's webhook docs](https://docs.argil.ai/pages/webhook-events/introduction).

## Delivery Semantics

* Return a `2xx` within **10 seconds**, then process asynchronously. Redirects are not followed.
* Failed deliveries retry after 30 s, 2 min, 10 min, 1 h, 6 h, then every 12 h for 3 days.
* Delivery is at least once and can arrive out of order.
* Answer `410 Gone` to disable the webhook. Three days of failures disables it too and emails the workspace owner. Updating a disabled webhook enables it again.

## Environment Variables

```bash
ARGIL_WEBHOOK_SECRET=whsec_xxxxx   # full value incl. whsec_; handlers fail closed (500) without it

```

## Local Development

```bash
# Express / Next.js (use 8000 for FastAPI). No install or account required.
npx hookdeck-cli listen 3000 argil --path /webhooks/argil

```

Create a webhook with the printed URL as `callbackUrl` (see [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/argil-webhooks/references/setup.md)), then render a video or train an avatar.

## Reference Materials

* [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/argil-webhooks/references/overview.md) - Event catalog, payload fields, delivery and retries
* [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/argil-webhooks/references/setup.md) - Creating, listing, updating and deleting webhooks through the API, the one-time secret
* [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/argil-webhooks/references/verification.md) - Library and manual verification, gotchas, debugging