Agent skill
Agora Webhooks Skill
Receive and verify Agora webhooks (Agora Notifications / NCS). Use when setting up Agora webhook handlers, debugging Agora-Signature verification, or handling RTC channel events such as channel create (101), channel destroy (102) and broadcaster join (103).
Install this skill
npx skills add hookdeck/webhook-skills --skill agora-webhooks
When to Use This Skill
- How do I receive Agora webhooks (Agora Notifications)?
- How do I verify the
Agora-Signatureheader? - How do I handle RTC channel events (
eventType101, 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.
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);
}
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/, examples/nextjs/, 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
AGORA_WEBHOOK_SECRET=your_agora_signing_secret # Signing Secret from Agora Console
Local Development
# Start tunnel (no account needed)
npx hookdeck-cli listen 3000 agora --path /webhooks/agora
Reference Materials
- references/overview.md - Agora Notifications concepts, events and delivery
- references/setup.md - Configuring the webhook in Agora Console
- references/verification.md - Signature verification details