Agent skill

RudderStack Webhooks Skill

Receive and authenticate RudderStack HTTP Webhook destination calls. Use when setting up a RudderStack webhook endpoint, verifying the Basic Auth Authorization header, debugging 401s, or handling forwarded track, identify, page, screen, group and alias events.

Install this skill

npx skills add hookdeck/webhook-skills --skill rudderstack-webhooks


RudderStack's HTTP Webhook destination forwards events (track, identify, page, screen, group, alias) to your endpoint. Webhooks are not signed. With Basic Auth selected, every request carries Authorization: Basic base64(username:password) and your endpoint authenticates the request by comparing those credentials with the ones you stored.

A static shared credential authenticates the sender, not the body, and gives no replay protection (no signature, no timestamp). Use HTTPS and rotate the credential if it leaks.

When to Use This Skill

  • How do I receive RudderStack webhooks?
  • How do I verify the HTTP Webhook destination's Basic Auth header?
  • Does RudderStack sign webhook payloads? (No: use the shared Basic credential)
  • How do I handle track and identify events from RudderStack?
  • Why does my RudderStack webhook endpoint return 401?

Verification (core)

const crypto = require('crypto');

// Hash both sides: equal-length buffers, so timingSafeEqual never throws.
const safeEqual = (a, b) => crypto.timingSafeEqual(
  crypto.createHash('sha256').update(a, 'utf8').digest(),
  crypto.createHash('sha256').update(b, 'utf8').digest());

function verifyRudderstackRequest(headers, user, pass) {
  const m = /^basic\s+([A-Za-z0-9+/]+={0,2})\s*$/i.exec(headers['authorization'] || '');
  if (!m || !user || !pass) return false;
  const decoded = Buffer.from(m[1], 'base64').toString('utf8');
  const i = decoded.indexOf(':');                 // split on the first colon
  if (i === -1) return false;
  const userOk = safeEqual(decoded.slice(0, i), user);   // evaluate both,
  const passOk = safeEqual(decoded.slice(i + 1), pass);  // no short-circuit
  return userOk && passOk;
}
  • Header: Authorization: Basic base64(username:password); the scheme is case-insensitive.
  • Python: hmac.compare_digest on bytes (encode both sides; str raises on non-ASCII).
  • Return 401 on a missing, malformed or mismatched header, 400 for an unparseable body, 200 once handled. RudderStack aborts a 4XX with no retries and retries 5XX and 429 for 3 hours, so return 503 for a temporary problem.
  • The method is user-chosen: accept POST, PUT, PATCH, GET and DELETE, and accept an empty body.
  • Read the body raw (express.raw({ type: '*/*' })) and authenticate before parsing it.
  • Bearer Token and API Key auth types are not covered.

For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.

Event Types

With "Send the event payload as is" (the default) the body is the RudderStack event and type says which kind; for track, event holds the event name. Batching sends an array of events; custom mappings can emit any shape.

typeSent When
trackA user performs an action (event, properties)
identifyA user is identified (traits)
pageA web page is viewed
screenA mobile screen is viewed
groupA user is associated with a group or account
aliasTwo user identities are merged

Environment Variables

RUDDERSTACK_WEBHOOK_USERNAME=your-username   # Destination > Authentication > Basic Auth
RUDDERSTACK_WEBHOOK_PASSWORD=your-password

Local Development

RudderStack can't reach localhost. Tunnel with the Hookdeck CLI (no account required):

npx hookdeck-cli listen 3000 rudderstack --path /webhooks/rudderstack

Use the printed URL as the destination's Base URL.

Resources


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Nov 5, 2026

View on GitHub →