# Argyle Webhooks

Argyle is a payroll and income verification API. A webhook subscription reports account, data-resource
and verification changes (accounts, gigs, identities, items and more) for every user of your API key.
When the subscription has a `secret`, each delivery carries an `X-Argyle-Signature` header: the hex
HMAC-SHA512 of the **raw request body**.

## When to Use This Skill

* How do I receive Argyle webhooks?
* How do I verify the Argyle webhook signature (`X-Argyle-Signature`, HMAC-SHA512)?
* Why does my Argyle webhook signature verification fail or return 401?
* How do I handle `accounts.connected`, `accounts.failed` or `gigs.fully_synced` events?

## Setup Note

Set a secret on the Argyle webhook subscription. Without one, deliveries are unsigned and fail verification.

Argyle does not generate the secret: you choose it when you create the subscription (Console or
`POST /v2/webhooks`). Argyle's docs don't name the HTTP method. Hookdeck's Argyle source pre-selects
POST without managing the method, so any other method the sender uses must also be allowed on the source.

## Verification (core)

```javascript
const crypto = require('crypto');

// rawBody: Buffer of the exact bytes received (express.raw({ type: '*/*' })), never parsed first.
function verifyArgyleSignature(rawBody, signature, secret = process.env.ARGYLE_WEBHOOK_SECRET) {
  if (!secret) return false;
  if (typeof signature !== 'string' || !/^[0-9a-fA-F]{128}$/.test(signature)) return false;
  const expected = crypto.createHmac('sha512', secret).update(rawBody).digest('hex');
  const a = Buffer.from(signature.toLowerCase());
  const b = Buffer.from(expected);
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

```

* Header: `X-Argyle-Signature`, the **whole value** is one bare hex digest (128 characters, no prefix, no list).
* Signed content: the raw body bytes only. No timestamp or id is signed, so there is **no replay window**
  to check. Make handlers idempotent instead (deliveries can repeat).
* The HMAC key is the secret's UTF-8 bytes, used as-is (not hex- or base64-decoded). Hex is
  case-insensitive: validate length and alphabet, lowercase, then compare in constant time.
* Missing or empty header, malformed value or mismatch: `401`. Verify **before** parsing, for every method.
* Accept `GET`, `POST`, `PUT`, `PATCH` and `DELETE` on the route; a request with no body is verified over
  zero bytes and, once verified, acknowledged with `200`.
* Answer `200` within 10 seconds. Argyle retries once after 30 s, only for 429, 500, 502, 503 or 504.

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

## Common Event Types

Dispatch on the body's top-level `event` field (`<resource>.<action>`). No header names the type.

| Event | Sent When |
| --- | --- |
| `accounts.added` | A new account is added for a user |
| `accounts.connected` | An account connects successfully |
| `accounts.failed` | An account connection fails |
| `accounts.removed` | An account is removed |
| `accounts.updated` | An account changes |
| `gigs.added` / `gigs.updated` / `gigs.removed` | Gig data changes |
| `gigs.partially_synced` / `gigs.fully_synced` | Gig data sync progresses or completes |
| `identities.added` / `identities.updated` | Identity data changes |
| `items.updated` / `items.removed` | A supported employer or platform changes |

Argyle documents 46 event types in all; the examples handle these 14 and log the rest. See
[references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/argyle-webhooks/references/overview.md).

## Environment Variables

```bash
ARGYLE_WEBHOOK_SECRET=your-subscription-secret   # the `secret` set on the subscription

```

## Local Development

Argyle can't reach `localhost`. Tunnel with the Hookdeck CLI (no account required):

```bash
npx hookdeck-cli listen 3000 argyle --path /webhooks/argyle

```

Use the printed URL as the subscription `url`.

## Resources

* [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/argyle-webhooks/references/overview.md) - What Argyle webhooks are, payload shape, events, delivery
* [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/argyle-webhooks/references/setup.md) - Create a subscription with a secret, methods, local testing
* [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/argyle-webhooks/references/verification.md) - Signature details, gotchas, debugging 401s
* [examples/](https://github.com/hookdeck/webhook-skills/tree/main/skills/argyle-webhooks/examples/) - Runnable Express, Next.js and FastAPI handlers with tests