# Iterable Webhooks

Iterable (marketing automation) sends **system webhooks** to your server whenever an event
occurs anywhere in a project: email sends, opens, clicks and bounces, SMS, push, in-app,
journey exits and more. Payloads are **not signed**. The only authentication is the
`Authorization` header, if you choose Basic or OAuth 2.0 on the webhook.

## When to Use This Skill

* How do I receive Iterable webhooks?
* How do I verify the Iterable webhook `Authorization` header (Bearer or Basic)?
* Does Iterable sign webhook payloads? (No: static credential header only)
* Why does my Iterable webhook endpoint return 401?
* How do I handle `emailSend`, `emailOpen`, `emailClick`, `emailBounce` or `emailUnSubscribe`?

## Verification (core)

On the system webhook, set **Authentication** to Basic or OAuth 2.0 and fill in
**Authentication Token**. Iterable then sends the credential as the `Authorization` header:

* **OAuth 2.0**: `Authorization: Bearer <token>` (you enter only the token)
* **Basic**: exactly the text you typed, e.g. `Authorization: Basic abc123` (not necessarily
  base64 `user:pass`)

Store the **whole header value, prefix included**, as `ITERABLE_AUTHORIZATION` and compare
the whole `Authorization` header to it. Don't strip or parse the prefix and never decode a
Basic value:

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

// Hash both sides: equal-length buffers, so timingSafeEqual never throws.
function safeEqual(a, b) {
  const ha = crypto.createHash('sha256').update(String(a)).digest();
  const hb = crypto.createHash('sha256').update(String(b)).digest();
  return crypto.timingSafeEqual(ha, hb);
}

function verifyIterableRequest(headers, expected = process.env.ITERABLE_AUTHORIZATION) {
  if (!expected) return false;                  // never accept when unconfigured
  const received = headers['authorization'];    // full value, e.g. "Bearer abc123"
  return Boolean(received) && safeEqual(received, expected);
}

```

Return `401` when this fails. Python uses `hmac.compare_digest` on **bytes** (encode both
sides; `str` raises on non-ASCII). Read the body raw (`express.raw({ type: '*/*' })`) and
parse JSON only after the check. See [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/iterable-webhooks/references/verification.md).

> **A static shared credential authenticates the sender, not the body, and gives no replay
> protection** (no signature, no timestamp). Use HTTPS (Iterable requires it) and rotate the
> credential if it leaks.

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

## Event Types

The body has `eventName`, `email` (or `userId`) and a `dataFields` object. Names are
case-sensitive and exactly as Iterable sends them, including `emailUnSubscribe`:

| `eventName` | Fires when |
| --- | --- |
| `emailSend` | A blast or triggered email is sent |
| `emailOpen` | A recipient opens an email |
| `emailClick` | A recipient clicks a link |
| `emailBounce` | An email bounces (`dataFields.recipientState`) |
| `emailComplaint` | A recipient reports spam |
| `emailSubscribe` / `emailUnSubscribe` | Subscription changes (universal to all channels) |
| `smsSend`, `pushSend`, `inAppOpen`, `webPushSend`, `whatsAppSend`, `rcsSend` | Other channels |
| `journeyExit` | A user exits a journey |

The full list is in [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/iterable-webhooks/references/overview.md).

## Delivery

2-second timeout; retries up to 3 times only on `429`, `502`, `503`, `504` (other 4xx/5xx,
including `401`, are not retried). Delivery is not guaranteed, order is not guaranteed and
duplicates are possible: respond fast, queue the work, and deduplicate.

## Environment Variables

```bash
ITERABLE_AUTHORIZATION=Bearer 00000000-0000-0000-0000-000000000000   # full header value

```

## Local Development

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

```

No account required. Use the printed URL as the Endpoint URL (Iterable requires `https://`).

## Resources

* [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/iterable-webhooks/references/overview.md) - Event names, payload fields, delivery behavior
* [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/iterable-webhooks/references/setup.md) - Creating a system webhook and choosing authentication
* [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/iterable-webhooks/references/verification.md) - Header check in Node, Python and Next.js, gotchas
* [examples/](https://github.com/hookdeck/webhook-skills/tree/main/skills/iterable-webhooks/examples/) - Runnable Express, Next.js and FastAPI handlers with tests