Events
Webhooks
Webhooks notify your back-end when a customer's Exchange payout is issued. The customer picks store credit (with a destination: applied to an existing order or saved as a code for next time) or cash to bank — and your handler routes to the right downstream flow.
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 Repurch issues their payout. Your dashboard at Settings → Integrations controls which options are offered to your customers — most retailers enable all three.
creditStore credit (boosted)
The customer's payout is store credit at your retailer, pre-boosted by whatever multiplier your team configured (e.g. 1.10× for a 10% top-up). The credit_destination field tells your handler whether to apply it to an existing order or mint a single-use code for later.
cashCash to bank (unboosted)
Funds land in the customer's nominated bank account at the base sale price. No boost. Use this when the customer doesn't want a retention play with your brand. Your handler triggers a BACS or Stripe payout.
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. Your handler mints the code in your gift card or loyalty system; the code value + expires_at are pre-populated by Repurch.
redemption.created
Fired once the customer's trade-in sells and they confirm their payout choice. The payload carries everything you need to route the payout to the right downstream system.
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
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,
original_order_id, code, expires_at } = event.data;
if (customer_choice === "cash") {
// Push the payout to the customer's bank via your payment
// processor. Unboosted base amount.
await issueBacsPayout({
customer_id,
amount_pence: value_pence,
reference: id,
});
} else if (customer_choice === "credit") {
// value_pence is the boosted amount — pre-applied by Repurch.
// Where it lands depends on credit_destination.
if (event.data.credit_destination === "existing_order") {
// Partial refund to the original payment method for the
// order the customer already placed.
await stripe.refunds.create({
payment_intent: getPaymentIntentForOrder(
event.data.original_order_id
),
amount: value_pence,
reason: "requested_by_customer",
metadata: { repurch_redemption_id: id },
});
} else {
// Single-use code to use at a future checkout.
await giftCards.create({
code,
discount_pence: value_pence,
customer_id,
expires_at,
single_use: true,
});
}
}
}
// Acknowledge within 5 seconds. Repurch retries 5xx and timeouts
// with exponential back-off for up to 24 hours.
res.status(200).json({ ok: true });
});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.