API reference
Webhooks
Set an address and we'll POST a JSON event to it when something happens on your shops. You can also set it up on the API page in the app. Answer with any 2xx within 10 seconds; anything else shows as a failed delivery there.
Events
| Event | When |
|---|---|
listing.sold | Something sold |
offer.received | A buyer made an offer |
offer.updated | A buyer took your counter, turned it down or withdrew |
question.asked | A buyer sent a message |
order.completed | A buyer said it arrived and it's all good |
order.problem | A buyer reported a problem with an order |
order.refunded | An order was cancelled or refunded |
payout.sent | Money went to your PayPal |
review.created | A buyer left a review |
What we send
The object in data.object is the same shape the API returns: an offer, an order (for listing.sold, order.completed and payout.sent) a message (for question.asked) or a review (for review.created). The Resell-Event header repeats the type.
{
"id": "evt_8c1d2f…",
"object": "event",
"type": "offer.received",
"created_at": "2026-10-02T18:00:00.000Z",
"data": {
"object": {
"object": "offer",
"id": "5b0f…",
"status": "open",
"amount": 150,
"note": "Could pick up this weekend.",
"listing": {
"id": "8d1e6c2a-…",
"title": "Yellow Le Creuset dutch oven, 5.5 qt",
"price": 185
},
"shop": {
"slug": "maya",
"name": "Maya's closet"
},
"buyer": {
"name": "Jess"
}
}
}
}When it fails
If your server doesn't answer with a 2xx, we try again about 5 minutes, 30 minutes, 2 hours, 6 hours and a day later, six tries in all. Every try carries the same event id, so keep the ones you've handled and skip repeats. Retries can arrive after newer events, so go by created_at, or fetch the object from the API, rather than by arrival order.
If deliveries keep failing for three days, we turn the webhook off and say so on the API page. Fix your server, then save the webhook again to switch it back on.
Checking it's us
Each request has a Resell-Signature: t=1759428000,v1=5f2b… header. v1 is an HMAC-SHA256 of {t}.{raw body} with your signing secret. Compare it in constant time, and ignore anything older than five minutes.
import { createHmac, timingSafeEqual } from "node:crypto";
// In your handler, with the raw request body as a string
export function verify(rawBody, header, secret) {
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
const expected = createHmac("sha256", secret)
.update(`${parts.t}.${rawBody}`)
.digest("hex");
const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300;
return fresh && timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1 ?? ""));
}Endpoints
Your webhook
/v1/webhooksWhere we send events, which ones, and how the last delivery went.
curl https://api.resell.store/v1/webhooks \
-H "Authorization: Bearer $RESELL_KEY"{
"object": "webhook",
"url": "https://example.com/hooks/resell",
"events": [
"listing.sold",
"offer.received"
],
"enabled": true,
"secret_last4": "9fQa",
"last_delivery": {
"at": "2026-10-02T18:30:02.000Z",
"status": 200,
"error": null
}
}Set your webhook
/v1/webhooksSets the address we POST events to and which events to send. The first time, the response includes the signing secret; keep it to check the Resell-Signature header. Events: listing.sold, offer.received, offer.updated, question.asked, order.completed, order.problem, order.refunded, payout.sent, review.created.
Body
urlstringrequiredeventsstring[]- Which events to send. Defaults to listing.sold, offer.received and question.asked.
enabledboolean
curl -X PUT https://api.resell.store/v1/webhooks \
-H "Authorization: Bearer $RESELL_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/hooks/resell",
"events": [
"listing.sold",
"offer.received"
]
}'{
"object": "webhook",
"url": "https://example.com/hooks/resell",
"events": [
"listing.sold",
"offer.received"
],
"enabled": true,
"last_delivery": null,
"secret": "whsec_…"
}Stop webhooks
/v1/webhookscurl -X DELETE https://api.resell.store/v1/webhooks \
-H "Authorization: Bearer $RESELL_KEY"{
"object": "webhook",
"url": null,
"events": [],
"enabled": false
}New signing secret
/v1/webhooks/rotate-secretMakes a new signing secret. The old one stops working at once.
curl -X POST https://api.resell.store/v1/webhooks/rotate-secret \
-H "Authorization: Bearer $RESELL_KEY"{
"object": "webhook",
"url": "https://example.com/hooks/resell",
"events": [
"listing.sold",
"offer.received"
],
"enabled": true,
"last_delivery": {
"at": "2026-10-02T18:30:02.000Z",
"status": 200,
"error": null
},
"secret": "whsec_…"
}Send a test event
/v1/webhooks/testSends a ping event to your address now and tells you what your server answered.
curl -X POST https://api.resell.store/v1/webhooks/test \
-H "Authorization: Bearer $RESELL_KEY"{
"object": "webhook_test",
"event": "evt_…",
"status": 200,
"error": null
}