Gareth Wilson Gareth Wilson

Guide to Redo Webhooks: Features and Best Practices

Published


Redo webhooks tell your systems when a return is created or changes. Redo runs returns and exchanges for e-commerce stores, and its webhooks let a warehouse system, 3PL, ERP or support tool react to each return as it moves through processing, instead of polling the API for changes.

This guide covers the return events and payload, how to subscribe and authenticate, how backfill and replay work, and how to receive Redo webhooks reliably.

What are Redo webhooks?

Redo webhooks are HTTP POST requests with a JSON body, sent to an HTTPS URL you register through the Redo API. You subscribe to topics, and when a return in your store is created or updated, Redo sends the whole return and its order.

Redo does not sign webhooks. When you create a subscription you supply a secret of your own, and Redo sends it on every delivery as Authorization: Bearer <secret>. Your receiver compares that header with the value it expects.

Redo webhook features

FeatureDetails
ConfigurationAPI: POST /v2.2/stores/{storeId}/webhooks
Topicsreturn.created, return.updated
Body type valuescreated, updated, backfill
Method and formatPOST, JSON
EndpointHTTPS required
AuthenticationAuthorization: Bearer <secret>, with a secret you generate
Signature and timestampNone
Expected response2xx within 5 seconds
RetriesMultiple times before the event is discarded; count and schedule not published
OrderingIn order per return, not across returns
ReplayPOST /v2.2/webhooks/{webhookId}/replay with a time range

Redo webhook events

You subscribe with topic names, and each delivery tells you what happened in its type field:

typeMeaning
createdA customer or team member created a new return
updatedAn existing return changed, for example its status or items
backfillA historical event sent during webhook setup or replay

The backfill type matters from day one. Redo sends historical events during webhook setup, so a new receiver can get backfill requests for returns that already exist before any live event arrives. Redo's docs tell you to handle all three types.

The payload

This trimmed example is based on the one in Redo's documentation:

{
  "type": "created",
  "at": "2023-08-18T12:00:00Z",
  "merchantName": "Example Merchant",
  "return": {
    "id": "64df65d4c5a4ca3eff4b4e43",
    "status": "pending",
    "createdAt": "2023-08-18T12:00:00Z",
    "updatedAt": "2023-08-18T12:00:00Z",
    "order": { "id": "shopify-order-123" },
    "items": [
      {
        "id": "item-1",
        "orderItem": { "id": "order-123/1" },
        "quantity": 1,
        "reason": "Too big",
        "upc": "012345678905",
        "processingImages": [],
        "customerImages": []
      }
    ],
    "totals": {
      "refund": { "amount": { "amount": "50.00", "currency": "USD" } }
    }
  },
  "order": {
    "id": "shopify-order-123",
    "items": [
      {
        "id": "order-123/1",
        "quantity": 1,
        "product": { "externalId": "product-456", "name": "T-Shirt" }
      }
    ]
  }
}

The fields you will use most:

  • type: created, updated or backfill.
  • at: when the event occurred, in ISO 8601.
  • merchantName: the merchant team that owns the return, useful when one receiver handles several merchants.
  • return: the complete return, including source (the customer's contact and address), destination, giftCards and totals for refund, exchange, store credit and charge.
  • return.items[]: each returned item, with upc (null if no barcode was recorded), processingImages added by the merchant or warehouse, and customerImages uploaded by the customer.
  • order: the original order.

Inspect Redo webhooks as they arrive. Create a subscription with a Hookdeck Inspect URL to see the backfill and live events Redo sends, with no account or setup required.

Setting up Redo webhooks

Subscriptions are managed through the Redo API, authenticated with your Redo API secret.

  1. Generate a long random subscriber secret, for example with openssl rand -hex 32. This is separate from your API secret.
  2. Create the subscription:
curl -X POST "https://api.getredo.com/v2.2/stores/$STORE_ID/webhooks" \
  -H "Authorization: Bearer $REDO_API_SECRET" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/webhooks/redo",
    "secret": "YOUR_LONG_RANDOM_SUBSCRIBER_SECRET",
    "topics": ["return.created", "return.updated"]
  }'

All three fields are required, and the URL must use HTTPS. To manage subscriptions afterwards:

  • List them with GET /v2.2/stores/{storeId}/webhooks.
  • Update one with PATCH /v2.2/webhooks/{webhookId}. Redo's example changes topics.
  • Delete one with DELETE /v2.2/webhooks/{webhookId}.

To route through Hookdeck, create a Hookdeck source with the Redo type and use its URL as the subscription url. The source has one field, Subscription Secret. Paste the secret you set on the subscription. Hookdeck expects Redo's Bearer prefix in front of it, so you don't need to type that, and pasting the full Bearer 3f9a... value works too. Hookdeck answers Redo with a 200 either way, but a request with a missing or wrong value is recorded as failed verification and never reaches your service. The source accepts POST, which is the method Redo uses.

Securing Redo webhooks

Redo's own example compares the header with `Bearer ${WEBHOOK_SECRET}` using !==. The check is right, but a constant-time comparison is better:

const crypto = require('crypto');
const express = require('express');

// The whole header value: "Bearer " followed by your subscriber secret.
const EXPECTED = process.env.REDO_AUTHORIZATION;

function safeEqual(received, expected) {
  const a = Buffer.from(received, 'utf8');
  const b = Buffer.from(expected, 'utf8');
  // timingSafeEqual throws on unequal lengths, so check first.
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

const app = express();

app.post('/webhooks/redo', express.raw({ type: '*/*' }), (req, res) => {
  const received = req.get('authorization');
  if (!EXPECTED || typeof received !== 'string' || !safeEqual(received, EXPECTED)) {
    return res.sendStatus(401);
  }

  let event;
  try {
    event = JSON.parse(req.body.toString('utf8'));
  } catch {
    return res.sendStatus(400);
  }

  // Queue it and answer within Redo's 5-second window.
  res.sendStatus(200);
});

The same check in Python:

import hmac
import os

EXPECTED = os.environ.get("REDO_AUTHORIZATION", "")


def verify_redo_request(headers) -> bool:
    if not EXPECTED:
        return False  # never accept when unconfigured
    received = headers.get("authorization")
    if not received:
        return False
    # Compare bytes: compare_digest raises TypeError on a non-ASCII str.
    return hmac.compare_digest(received.encode("utf-8"), EXPECTED.encode("utf-8"))

A Bearer secret authenticates the sender, not the body. It cannot detect a modified payload, and with no signature or timestamp a captured request can be replayed. Keep the endpoint on HTTPS, which Redo requires anyway, use a long random secret, and keep it out of logs. Redo documents updating a subscription's topics with PATCH but not changing its secret, so if the secret might have leaked and a PATCH with a new secret is not accepted, create a new subscription and delete the old one.

Make Redo webhooks production-ready. Hookdeck Event Gateway checks the Authorization value on every request, responds to Redo straight away so you stay inside the 5-second window, and retries delivery to your service on your schedule.

Redo webhook limitations and pain points

A five-second window and an unknown retry budget

The Problem: A slow or unavailable receiver can lose return events.

Why It Happens: Redo asks for a 2xx within 5 seconds and retries "multiple times before discarding the event". It does not say how many times or over what period.

Workarounds:

  • Store the request and return 200 before doing any work.
  • After an outage, use the replay endpoint to resend the affected time range.

How Hookdeck Can Help: Hookdeck accepts each request and responds to Redo immediately, then delivers to your service with retries you configure. Events that still fail stay in Hookdeck, where you can fix the problem and retry them, instead of being discarded.

Backfill on setup

The Problem: A new subscription can send historical events that a receiver built for live traffic may not expect, possibly many at once.

Why It Happens: Redo sends backfill events during webhook setup and replay.

Workarounds:

  • Handle backfill explicitly, usually as an upsert that does not trigger customer-facing actions such as emails.
  • Make sure the receiver can absorb a burst before you create the subscription.

How Hookdeck Can Help: A filter on type can send backfill events to a separate connection, and a max delivery rate spreads the burst out so it does not overload your service.

Ordering only per return

The Problem: Events for different returns arrive interleaved, and processing them in parallel can apply an older update after a newer one for the same return.

Why It Happens: Redo delivers events in order for each return, but different returns may arrive in any order.

Workarounds:

  • Compare at with the last value you stored for that return, and ignore anything older.
  • Process events for the same return.id one at a time.

How Hookdeck Can Help: Hookdeck keeps the full history of each return's events, so you can see the order they arrived in and replay them if your handler got the sequence wrong.

Duplicate deliveries

The Problem: The same event can arrive more than once, for example after a retry or a replay.

Why It Happens: Redo retries until it gets a 2xx, and replay resends events you may already have.

Workarounds:

How Hookdeck Can Help: Deduplication can drop requests whose return.id and at match a recent one.

Best practices

Store the whole Authorization value

Keep Bearer and the secret together as one expected value, and compare it whole. That way your check matches exactly what Redo sends.

Process by the at timestamp

Treat at as the order of truth for each return, and do not let an older event overwrite newer state.

Handle every type, including unknown ones

Handle created, updated and backfill, and log and accept anything else rather than failing. Returning an error for an unexpected type only causes retries.

Use replay to recover

After downtime, call POST /v2.2/webhooks/{webhookId}/replay with a startTime and endTime covering the gap. Replayed events arrive as backfill, and your idempotency key keeps them from being applied twice.

Acknowledge fast and process asynchronously

Verify, store, return 200, then update your systems. See why to process webhooks asynchronously.

Conclusion

Redo webhooks send the complete return and order whenever a return is created or updated, with a backfill type for history and a replay endpoint for recovery. They are authenticated only by a Bearer secret you choose, so compare the whole header in constant time over HTTPS, answer within five seconds, and make processing idempotent on return.id and at.

Hookdeck Event Gateway checks the header, answers Redo immediately, absorbs backfill bursts, and keeps failed events for retry instead of letting them be discarded.

Get started with Hookdeck for free and handle Redo webhooks reliably in minutes.


Gareth Wilson

Gareth Wilson

Product Marketing

Multi-time founding marketer, Gareth is PMM at Hookdeck and author of the newsletter, Community Inc.