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-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.

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
101Channel create
102Channel destroy
103Broadcaster joins channel
104Broadcaster leaves channel
105, 106, 107, 108, 111, 112Other 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


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Nov 13, 2026

View on GitHub →