Agent skill

Rebrandly Webhooks Skill

Receive and authenticate Rebrandly webhooks. Use when setting up a Rebrandly Clickstream or account webhook endpoint, checking the authentication header, debugging 401s, or handling link click events.

Install this skill

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


Rebrandly (branded short links) sends a JSON webhook for link clicks. There are two products: enterprise Clickstream webhooks, where you give Rebrandly an endpoint URL, an HTTP method (POST or PUT) and an authentication header key and value; and the self-serve Webhooks feature in account settings.

Rebrandly webhooks are not signed. Add an authentication header to the webhook and enter it here.

Requests are unsigned. You authenticate the sender with a secret header you add yourself.

When to Use This Skill

  • How do I receive Rebrandly webhooks?
  • How do I authenticate Rebrandly Clickstream requests?
  • Does Rebrandly sign webhook payloads? (No: use a shared header)
  • How do I handle Rebrandly click events (data[] batches)?
  • Why does my Rebrandly webhook endpoint return 401?

Verification (core)

const crypto = require('crypto');

function safeEqual(a, b) {
  const ba = Buffer.from(String(a), 'utf8');
  const bb = Buffer.from(String(b), 'utf8');
  return ba.length === bb.length && crypto.timingSafeEqual(ba, bb);
}

function verifyRebrandlyRequest(headers, secret = process.env.REBRANDLY_WEBHOOK_SECRET,
    headerName = process.env.REBRANDLY_WEBHOOK_HEADER || 'Authorization') {
  if (!secret) return false;                       // never accept when unconfigured
  const received = headers[headerName.toLowerCase()];
  return typeof received === 'string' && received.length > 0 && safeEqual(received, secret);
}
  • The header name is yours: set REBRANDLY_WEBHOOK_HEADER (default Authorization), look it up case-insensitively, and compare the whole value with REBRANDLY_WEBHOOK_SECRET. Do not strip or parse a Bearer prefix; if you typed one at Rebrandly, it is part of the secret.
  • Rebrandly's own blog names the self-serve header inconsistently (X-Rebrandly-Secret and apikey). Capture one real delivery and set REBRANDLY_WEBHOOK_HEADER to the header that arrives.
  • Missing header or mismatch: 401. Check it first, for every HTTP method.
  • Python: hmac.compare_digest on bytes (encode both sides; str raises on non-ASCII).
  • Read the body raw (express.raw({ type: '*/*' })) and parse it only after the check. Handle an empty body.
  • Security limits: 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.

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

HTTP Methods

Clickstream uses the POST or PUT method you give Rebrandly at setup, so the examples accept GET, POST, PUT, PATCH and DELETE on /webhooks/rebrandly. Hookdeck's Rebrandly source pre-selects POST without managing the method, so any other method the sender uses must also be allowed on the source.

Event Types

Only click events exist; there is no event-type field. The body is { "data": [ click, ... ], "key": "..." }. Loop over data[].

EventSent When
clickA short link is clicked (one entry per click in data[])

Each click has timestamp, client (language, location, agent, session), referral and route (link id, slashtag, creator, domain, destination). Each event is sent once; there are no retries.

Environment Variables

REBRANDLY_WEBHOOK_HEADER=Authorization        # header name you gave Rebrandly
REBRANDLY_WEBHOOK_SECRET=your-shared-secret   # exact, whole header value

Local Development

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

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

Resources


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Nov 10, 2026

View on GitHub →