Events
Webhooks
Webhooks notify your back-end when a Repurch event needs downstream action. Two event families ship today: redemption.created for Exchange payouts, and collection.scheduled for Clearance pickups. Both deliver to the same endpoint; route on the type field. Repurch pays customers who choose cash directly; for customers who choose credit, Repurch settles the sale proceeds to you and you honour the credit at your checkout.
Setting up an endpoint
Configure your HTTPS endpoint via PATCH /v1/partner/settings/integrations/webhook, or from Settings → Integrations in the partner dashboard. The endpoint must:
- Use HTTPS with a valid TLS certificate.
- Respond with
2xxwithin 5 seconds. - Tolerate duplicate deliveries — Repurch retries on timeout or 5xx.
Retries follow exponential back-off for up to 24 hours. After that, the event is parked on the dead-letter queue and surfaced in the dashboard for manual replay.
Payout choice
When a customer's old sofa sells, they pick how they are paid. Your dashboard at Settings → Exchange controls which options are offered to your customers — most retailers enable all three.
creditStore credit (boosted)
The customer's payout is store credit with you, pre-boosted by whatever your team configured (e.g. 1.10× for a 10% top-up). Repurch settles the base sale proceeds to your wallet; you fund the boost and honour the credit. The credit_destination field tells your handler whether to apply it to an existing order or whether the customer took a single-use code for later.
cashCash to bank (unboosted)
Repurch pays the base sale price (no boost) straight to the customer's bank. The customer submits their bank details to Repurch in the Exchange flow; you never handle them. No action is needed from your handler, and cash sales don't appear on your remittance.
When customer_choice is credit, credit_destination determines where the credit lands:
existing_orderApply to existing order
credit_destination = existing_order. Credit is applied as a partial refund to the order in original_order_id (typically the new sofa they bought from you under a trade-in plan). Your handler refunds to the original payment method via your payment processor.
codeSave as a single-use code
credit_destination = code. Customer wants to spend the credit later. Repurch generates the single-use code (value + expires_at) and gives it to the customer. At checkout, validate and spend it with POST /v1/partner/credits/redeem; optionally mirror it into your gift card or loyalty system.
redemption.created
Fired once the customer's trade-in sells and they confirm their payout choice. For credit, the payload carries everything you need to honour it; for cash it is informational, because Repurch pays the customer.
Example delivery
POST /repurch/redemptions HTTP/1.1
Host: furnitureco.example
Content-Type: application/json
User-Agent: Repurch-Webhooks/1.0
X-Repurch-Event: redemption.created
X-Repurch-Event-Id: evt_2d8f3a17c4b9e62a
X-Repurch-Signature: t=1749548531,v1=2c0f9...Payload (apply-to-existing-order example)
{
"id": "evt_2d8f3a17c4b9e62a",
"type": "redemption.created",
"created_at": "2026-06-10T09:42:11+00:00",
"data": {
"id": "rdm_8c2f1a9d4e7b6035",
"brand_id": "furniture-co",
"customer_id": "cust_4a91c2e5",
"customer_choice": "credit",
"credit_destination": "existing_order",
"value_pence": 52800,
"value": 528.00,
"currency": "GBP",
"code": null,
"expires_at": null,
"original_order_id": "ORD-2026-0042",
"related_listing_id": 12345,
"sale_completed_at": "2026-06-09T14:00:00+00:00"
}
}Fields
id string | Unique event identifier. Use it to deduplicate retries. |
type string | Always redemption.created for this event. |
created_at string | ISO 8601 UTC timestamp the event was emitted. |
data.id string | Stable redemption ID. Reference this in any logs or downstream records so a customer-support query can be traced back. |
data.brand_id string | The brand the redemption was issued against. Matches your workspace. |
data.customer_id string | Stable Repurch customer identifier. |
data.customer_choice string | One of credit | cash. Drives the top-level payout flow. |
data.credit_destination string | null | When customer_choice is credit, one of existing_order | code. null when customer_choice is cash. |
data.value_pence integer | Payout value in pence. Pre-boosted for credit; unboosted for cash. |
data.value number | Same value as GBP decimal. Always equal to value_pence / 100. |
data.code string | null | Single-use code, present when credit_destination is code. null otherwise. |
data.expires_at string | null | ISO 8601 expiry, present when credit_destination is code. null otherwise. |
data.original_order_id string | null | Order to credit against, present when credit_destination is existing_order. null otherwise. |
data.related_listing_id integer | The Exchange listing whose sale triggered this payout. Useful for support context. |
data.sale_completed_at string | ISO 8601 UTC timestamp the underlying sale completed (delivery to the buyer). |
Handler skeleton
// Node.js / Express
//
// Cash: Repurch pays the customer straight to their bank. Nothing
// for you to do, and the sale is not on your remittance.
// Credit: Repurch settles the sale proceeds to your wallet (48h after
// delivery) and you honour the credit, so this webhook tells your
// back-end where that credit lands.
app.post("/repurch/redemptions", express.json(), async (req, res) => {
const event = req.body;
if (event.type === "redemption.created") {
const { id, customer_choice, value_pence, customer_id,
credit_destination, original_order_id, code,
expires_at } = event.data;
if (customer_choice === "cash") {
// Informational only. Repurch has already collected the
// customer's bank details and pays them directly.
await crm.logTradeIn({ customer_id, reference: id, payout: "cash" });
} else if (customer_choice === "credit") {
// value_pence already includes whatever boost your team has
// configured. Where it lands depends on credit_destination.
if (credit_destination === "existing_order") {
// Partial refund to the original payment method for the
// order the customer already placed with you.
await stripe.refunds.create({
payment_intent: getPaymentIntentForOrder(original_order_id),
amount: value_pence,
reason: "requested_by_customer",
metadata: { repurch_redemption_id: id },
});
} else {
// Repurch generated the single-use code and gave it to the
// customer. Optionally mirror it into your gift card system;
// at checkout, validate + spend it with
// POST /v1/partner/credits/redeem.
await giftCards.mirror({
code,
discount_pence: value_pence,
customer_id,
expires_at,
});
}
}
}
// Acknowledge within 5 seconds. Repurch retries 5xx and timeouts
// with exponential back-off for up to 24 hours.
res.status(200).json({ ok: true });
});collection.scheduled
Fired when a Clearance sale's collection date is confirmed in the partner dashboard. Your logistics provider integration uses the payload to dispatch a pickup from the chosen warehouse to the buyer. The buyer's delivery window is included so route planners can slot the delivery leg into the same vehicle run.
Example delivery
POST /repurch/collections HTTP/1.1
Host: furnitureco.example
Content-Type: application/json
User-Agent: Repurch-Webhooks/1.0
X-Repurch-Event: collection.scheduled
X-Repurch-Event-Id: evt_a8b14e932cd0f76e
X-Repurch-Signature: t=1749728922,v1=8d4a1...Payload
{
"id": "evt_a8b14e932cd0f76e",
"type": "collection.scheduled",
"created_at": "2026-06-12T11:08:42+00:00",
"data": {
"id": "col_5b209a17e4f6c81d",
"brand_id": "furniture-co",
"order_id": 12345,
"order_reference": "#12345",
"product_id": 67890,
"scheduled_for": "2026-06-15",
"scheduled_at": "2026-06-12T11:08:42+00:00",
"warehouse_id": "f3c1a4b5-9d22-4e6f-8a01-7b2d4e8c9f10",
"warehouse_label": "Nottingham hub",
"buyer_window_start": "2026-06-12",
"buyer_window_end": "2026-06-22"
}
}Fields
id string | Unique event identifier. Use it to deduplicate retries. |
type string | Always collection.scheduled for this event. |
created_at string | ISO 8601 UTC timestamp the event was emitted. |
data.id string | Stable collection ID. Reference in your dispatch records for support traceability. |
data.brand_id string | The brand the collection belongs to. Matches your workspace. |
data.order_id integer | WooCommerce-side order ID for the sold item. |
data.order_reference string | Display-friendly order reference, e.g. #12345. |
data.product_id integer | Product ID of the line item being collected. |
data.scheduled_for string | The confirmed collection date (YYYY-MM-DD). Always inside the buyer's delivery window. |
data.scheduled_at string | ISO 8601 UTC timestamp the retailer pressed Schedule. |
data.warehouse_id string | null | The collection-source warehouse UUID. null if the brand has no warehouses configured. |
data.warehouse_label string | Display label for the warehouse, e.g. 'Nottingham hub'. Empty when no warehouse is assigned. |
data.buyer_window_start string | Start of the buyer's 10-day delivery window (YYYY-MM-DD). Useful for the delivery leg. |
data.buyer_window_end string | End of the buyer's 10-day delivery window (YYYY-MM-DD). |
Delivery semantics are identical to redemption.created: same endpoint (configured at Settings → Integrations), same HMAC signature scheme, same retry policy. Route on the type field.
Pull-based alternative
If you'd rather poll than receive webhooks, the same data is available via GET /v1/redemptions?since=<timestamp>. That endpoint returns the same payload shape under the data envelope. Most partners pick one or the other — you can run both during cutover.
Signature verification
Every delivery carries an X-Repurch-Signature header in the Stripe-compatible t=<timestamp>,v1=<hmac> format. The HMAC is SHA-256 over <timestamp>.<raw body> using your endpoint's signing secret as the key.
Signature verification ships in a future iteration. Until then, treat the webhook URL itself as the shared secret: use a long unguessable path component and reject requests to any other path.