Agent skill

Redo Webhooks Skill

Receive and authenticate Redo returns webhooks. Use when setting up a Redo webhook endpoint, checking the Authorization Bearer header sent with every delivery, debugging 401s, or handling return.created and return.updated events (plus backfill events) for e-commerce returns.

Install this skill

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


Redo (e-commerce returns and exchanges) sends a POST with a JSON body to your HTTPS endpoint when a return is created or updated. Payloads are not signed. The only authentication is a static Authorization: Bearer <your-subscriber-secret> header, where the secret is a value you generate and supply when you create the webhook subscription.

When to Use This Skill

  • How do I receive Redo webhooks?
  • How do I verify the Redo Authorization: Bearer header on a webhook?
  • Does Redo sign webhook payloads? (No: shared secret in the Authorization header only)
  • Why does my Redo webhook endpoint return 401?
  • How do I handle return.created, return.updated and backfill events?

Verification (core)

Redo sends Authorization: Bearer <your-subscriber-secret>. The examples compare the whole header value to the configured key: set REDO_AUTHORIZATION to the full value as Redo sends it, including the Bearer prefix. Don't strip, parse or base64-decode anything.

const crypto = require('crypto');

// Hash both sides: equal-length buffers, so timingSafeEqual never throws.
function safeEqual(a, b) {
  const ha = crypto.createHash('sha256').update(String(a)).digest();
  const hb = crypto.createHash('sha256').update(String(b)).digest();
  return crypto.timingSafeEqual(ha, hb);
}

function verifyRedoRequest(headers, expected = process.env.REDO_AUTHORIZATION) {
  if (!expected) return false;                  // never accept when unconfigured
  const received = headers['authorization'];    // full value, e.g. "Bearer your-subscriber-secret"
  return Boolean(received) && safeEqual(received, expected);
}

Return 401 when this fails (missing header or mismatch), 400 for a body that isn't a JSON object, and 2xx once handled (Redo wants a 2xx within 5 seconds, and retries otherwise). Read the body raw (express.raw({ type: '*/*' })) so any content type works, and parse it only after the credential check. Python and Next.js equivalents are in references/verification.md.

Security note: a static shared credential authenticates the sender, not the body, and gives no replay protection (no signature, no timestamp). Anyone who captures one request can replay it, so use HTTPS only, keep the secret out of logs and source control, and rotate it if it leaks (create a subscription with a new secret, then delete the old one; the docs show PATCH changing only topics).

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

Hookdeck Source Setup

In Hookdeck, create a source of type Redo (REDO). Hookdeck shows this setup note:

Paste the secret set on the Redo webhook subscription.

Paste the bare secret into the Subscription Secret field, without Bearer . Hookdeck accepts the Authorization header with or without the Bearer prefix.

Event Types

Subscribe with the topics array. The payload's type field carries the short form:

TopicPayload typeTriggered when
return.createdcreatedA customer or team member creates a return
return.updatedupdatedA return changes (status, items, ...)
(sent at setup or replay)backfillA historical event sent during webhook setup or replay

The body is { "type", "at", "merchantName", "return": {...}, "order": {...} }. Handle backfill gracefully. Events are ordered per return, not across returns, so order by at.

Environment Variables

REDO_AUTHORIZATION=Bearer your-subscriber-secret   # full header value, "Bearer " included

Local Development

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

No account required. Use the printed URL as the url when creating the Redo webhook subscription.

Resources


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Nov 6, 2026

View on GitHub →