Agent skill
Ably Webhooks Skill
Receive and verify Ably webhooks (outbound HTTP integration rules). Use when setting up Ably webhook handlers, debugging X-Ably-Signature verification, or handling channel.message, channel.presence, channel.lifecycle and channel.occupancy events, including batched, enveloped and msgpack payloads.
Install this skill
npx skills add hookdeck/webhook-skills --skill ably-webhooks
Covers Ably outbound webhook integration rules (generic HTTP endpoints). Lambda, Azure, Google Functions, Zapier and similar targets are different rule types.
When to Use This Skill
- How do I receive Ably webhooks?
- How do I verify the
X-Ably-Signatureheader? - Why does my Ably webhook signature verification fail?
- How do I handle
channel.message,channel.presence,channel.lifecycleorchannel.occupancyevents? - How do I handle batched, enveloped, non-enveloped or msgpack Ably webhook payloads?
Verification (core)
The HMAC key is the
keyValueonly. An Ably API key looks likeappId.keyId:keyValue. Use the part after the:as the HMAC key, not the whole key. Using the full API key is the most common reason verification fails.
Signing is opt-in: enable Sign with key on the integration rule. Ably then sends X-Ably-Signature (base64 HMAC-SHA256 of the raw request body bytes) and X-Ably-Key (names the API key used). There is no timestamp in the signature. The body may be JSON or msgpack and is signed either way.
const crypto = require('crypto');
function verifyAbly(rawBody /* Buffer */, signature, apiKey /* appId.keyId:keyValue */) {
const keyValue = apiKey.slice(apiKey.indexOf(':') + 1); // secret part only
const expected = crypto.createHmac('sha256', keyValue).update(rawBody).digest('base64');
try {
return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
} catch {
return false; // different lengths
}
}
Read the body as raw bytes for any content type: express.raw({ type: '*/*' }) in Express, request.arrayBuffer() in Next.js, await request.body() in FastAPI. Ably has no SDK helper for webhook verification, so all examples verify manually. See references/verification.md for gotchas.
For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.
Payload Shapes
| Mode | What you receive |
|---|---|
| Enveloped (default) | { source, appId, channel, site, ruleId, messages | presence } |
| Batched | { items: [{ webhookId, source, name, serial, timestamp, data: { channelId, ... } }] }. Always iterate items |
| Non-enveloped | Raw data as the body; metadata in x-ably-envelope-* headers (not covered by the signature) |
Messages and presence entries are raw: data may need decoding with Message.fromEncodedArray / PresenceMessage.fromEncodedArray, and presence action is a number (0 absent, 1 present, 2 enter, 3 leave, 4 update).
Common Event Types
Events are identified by the source field (and x-ably-envelope-source header when not enveloped):
| Source | Description |
|---|---|
channel.message | Messages published on a channel |
channel.presence | Members enter, leave or update presence |
channel.lifecycle | Channel created or discarded (name is channel.opened or channel.closed) |
channel.occupancy | Number of users on a channel changed |
For the full reference, see Ably outbound webhooks
Environment Variables
ABLY_API_KEY=appId.keyId:keyValue # the key selected in "Sign with key"; only keyValue is the HMAC key
Local Development
# Start tunnel (no account needed)
npx hookdeck-cli listen 3000 ably --path /webhooks/ably
Reference Materials
- references/overview.md - Ably webhook concepts, payload shapes, events
- references/setup.md - Create an integration rule and enable signing
- references/verification.md - Signature verification details and gotchas