API reference

Webhooks

Get told when something happens.

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

EventWhen
listing.soldSomething sold
offer.receivedA buyer made an offer
offer.updatedA buyer took your counter, turned it down or withdrew
question.askedA buyer sent a message
order.completedA buyer said it arrived and it's all good
order.problemA buyer reported a problem with an order
order.refundedAn order was cancelled or refunded
payout.sentMoney went to your PayPal
review.createdA 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.

POST to your address
{
  "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.

Node
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

GET/v1/webhooks
Needs a key with webhooks

Where we send events, which ones, and how the last delivery went.

Request
curl https://api.resell.store/v1/webhooks \
  -H "Authorization: Bearer $RESELL_KEY"
Response
{
  "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

PUT/v1/webhooks
Needs a key with webhooks

Sets 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

urlstringrequired
eventsstring[]
Which events to send. Defaults to listing.sold, offer.received and question.asked.
enabledboolean
Request
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"
    ]
  }'
Response
{
  "object": "webhook",
  "url": "https://example.com/hooks/resell",
  "events": [
    "listing.sold",
    "offer.received"
  ],
  "enabled": true,
  "last_delivery": null,
  "secret": "whsec_…"
}

Stop webhooks

DELETE/v1/webhooks
Needs a key with webhooks
Request
curl -X DELETE https://api.resell.store/v1/webhooks \
  -H "Authorization: Bearer $RESELL_KEY"
Response
{
  "object": "webhook",
  "url": null,
  "events": [],
  "enabled": false
}

New signing secret

POST/v1/webhooks/rotate-secret
Needs a key with webhooks

Makes a new signing secret. The old one stops working at once.

Request
curl -X POST https://api.resell.store/v1/webhooks/rotate-secret \
  -H "Authorization: Bearer $RESELL_KEY"
Response
{
  "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

POST/v1/webhooks/test
Needs a key with webhooks

Sends a ping event to your address now and tells you what your server answered.

Request
curl -X POST https://api.resell.store/v1/webhooks/test \
  -H "Authorization: Bearer $RESELL_KEY"
Response
{
  "object": "webhook_test",
  "event": "evt_…",
  "status": 200,
  "error": null
}