Guide to Redo Webhooks: Features and Best Practices
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
| Feature | Details |
|---|---|
| Configuration | API: POST /v2.2/stores/{storeId}/webhooks |
| Topics | return.created, return.updated |
Body type values | created, updated, backfill |
| Method and format | POST, JSON |
| Endpoint | HTTPS required |
| Authentication | Authorization: Bearer <secret>, with a secret you generate |
| Signature and timestamp | None |
| Expected response | 2xx within 5 seconds |
| Retries | Multiple times before the event is discarded; count and schedule not published |
| Ordering | In order per return, not across returns |
| Replay | POST /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:
type | Meaning |
|---|---|
created | A customer or team member created a new return |
updated | An existing return changed, for example its status or items |
backfill | A 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,updatedorbackfill.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, includingsource(the customer's contact and address),destination,giftCardsandtotalsfor refund, exchange, store credit and charge.return.items[]: each returned item, withupc(nullif no barcode was recorded),processingImagesadded by the merchant or warehouse, andcustomerImagesuploaded 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.
- Generate a long random subscriber secret, for example with
openssl rand -hex 32. This is separate from your API secret. - 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 changestopics. - 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_ 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
200before 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
backfillexplicitly, 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
atwith the last value you stored for that return, and ignore anything older. - Process events for the same
return.idone 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:
- Use
return.idplusatas the idempotency key, as Redo recommends. See our guide to webhook idempotency.
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.