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-Signature header?
  • Why does my Ably webhook signature verification fail?
  • How do I handle channel.message, channel.presence, channel.lifecycle or channel.occupancy events?
  • How do I handle batched, enveloped, non-enveloped or msgpack Ably webhook payloads?

Verification (core)

The HMAC key is the keyValue only. An Ably API key looks like appId.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

ModeWhat you receive
Enveloped (default){ source, appId, channel, site, ruleId, messages | presence }
Batched{ items: [{ webhookId, source, name, serial, timestamp, data: { channelId, ... } }] }. Always iterate items
Non-envelopedRaw 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):

SourceDescription
channel.messageMessages published on a channel
channel.presenceMembers enter, leave or update presence
channel.lifecycleChannel created or discarded (name is channel.opened or channel.closed)
channel.occupancyNumber 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


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Nov 2, 2026

View on GitHub →