# Agora Webhooks

## When to Use This Skill

* How do I receive Agora webhooks (Agora Notifications)?
* How do I verify the `Agora-Signature` header?
* How do I handle RTC channel events (`eventType` 101, 102, 103, 104)?
* Why is my Agora webhook signature verification failing?
* Why am I getting duplicate or out-of-order Agora notifications?

## Verification (core)

Every delivery carries `Agora-Signature` (HMAC-SHA1) and `Agora-Signature-V2` (HMAC-SHA256) over the same input. This skill verifies `Agora-Signature`: the **bare lowercase hex** HMAC-SHA1 (40 characters, no prefix) of the **raw request body**, keyed with the Signing Secret as UTF-8 bytes. No timestamp or id is signed, so there is no replay window to check.

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

function verify(rawBody, signature, secret) {           // rawBody: Buffer
  if (!signature || !secret) return false;
  if (!/^[0-9a-fA-F]{40}$/.test(signature)) return false; // validate before decoding
  const expected = crypto.createHmac('sha1', secret).update(rawBody).digest();
  const received = Buffer.from(signature.toLowerCase(), 'hex');
  return received.length === expected.length && crypto.timingSafeEqual(received, expected);
}

```

```python
import hashlib, hmac, re

def verify(raw_body: bytes, signature: str | None, secret: str | None) -> bool:
    if not signature or not secret or not re.fullmatch(r"[0-9a-fA-F]{40}", signature):
        return False
    expected = hmac.new(secret.encode("utf-8"), raw_body, hashlib.sha1).digest()
    return hmac.compare_digest(bytes.fromhex(signature), expected)

```

Read the body as raw bytes (`express.raw({ type: '*/*' })`, `request.arrayBuffer()`, `await request.body()`) and parse JSON only after the check. Missing, malformed or mismatched signature: respond `401`.

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

## Event Types

There is no event header. Dispatch on the body's numeric `eventType`, scoped by `productId` (`1` = RTC; `3`/`4`/`5` = Cloud Recording, Media Pull, Media Push, which have their own codes).

| `eventType` (RTC) | Meaning |
| --- | --- |
| `101` | Channel create |
| `102` | Channel destroy |
| `103` | Broadcaster joins channel |
| `104` | Broadcaster leaves channel |
| `105`, `106`, `107`, `108`, `111`, `112` | Other documented RTC codes (audience join/leave, role changes, and more); the examples log them. Check the Agora docs for each code's meaning |

Payload shape: `{noticeId, productId, eventType, notifyMs, sid, payload: {channelName, ts, uid, clientSeq, ...}}`. Deduplicate on `noticeId`; deliveries can repeat and arrive out of order.

## Environment Variables

```bash
AGORA_WEBHOOK_SECRET=your_agora_signing_secret   # Signing Secret from Agora Console

```

## Local Development

```bash
# Start tunnel (no account needed)
npx hookdeck-cli listen 3000 agora --path /webhooks/agora

```

## Reference Materials

* [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/agora-webhooks/references/overview.md) - Agora Notifications concepts, events and delivery
* [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/agora-webhooks/references/setup.md) - Configuring the webhook in Agora Console
* [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/agora-webhooks/references/verification.md) - Signature verification details