repurch/ docs
Dashboard ↗repurch.com ↗

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 2xx within 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.

credit

Store 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.

cash

Cash 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_order

Apply 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.

code

Save 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

http
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)

json
{
  "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

javascript
// 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.