Partner documentation

Everything you need to publish your sale and buyback listings on QuizzBuy and notify us of orders attributed to our traffic.

1. Get an API key

Contact your QuizzBuy representative (or the contact form). We generate a key for you in the format zzb_live_… — it is given to you only once, so keep it safe. Pass it on every request in the HTTP header:

Authorization: Bearer zzb_live_VOTRE_CLE

2. Verify your key

curl https://quizzbuy.com/api/v1/me \
  -H "Authorization: Bearer zzb_live_VOTRE_CLE"

# Réponse
{
  "company": { "id": "…", "name": "Votre société", "slug": "votre-societe" },
  "commission_rate": 0.05,
  "click_param": "zzb_click"
}

3. Publish your listings

A listing is either a buyback offer (BUYBACK — you take back a device at a given price), or a sale offer (SALE — a refurbished product you're selling). The submission is an upsert: resending the same externalId updates the listing.

curl -X POST https://quizzbuy.com/api/v1/listings \
  -H "Authorization: Bearer zzb_live_VOTRE_CLE" \
  -H "Content-Type: application/json" \
  -d '{
    "externalId": "ref-interne-123",
    "type": "BUYBACK",
    "title": "Reprise iPhone 15 Pro 256 Go",
    "brand": "Apple",
    "model": "iPhone 15 Pro",
    "category": "SMARTPHONE",
    "storage": "256 GB",
    "condition": "GOOD",
    "priceCents": 52000,
    "currency": "EUR",
    "url": "https://votre-site.fr/reprise/iphone-15-pro",
    "country": "FR"
  }'
  • category: SMARTPHONE, LAPTOP, SMARTWATCH, AUDIO or GAMING.
  • condition: LIKE_NEW, EXCELLENT, GOOD, FAIR or BROKEN.
  • priceCents: price in cents (52000 = €520).
  • Each new listing (or edit) goes through moderation before publication.

Update, list or deactivate:

# Lister vos annonces
curl "https://quizzbuy.com/api/v1/listings?type=BUYBACK&status=APPROVED" \
  -H "Authorization: Bearer zzb_live_VOTRE_CLE"

# Mise à jour partielle
curl -X PATCH https://quizzbuy.com/api/v1/listings/ID_ANNONCE \
  -H "Authorization: Bearer zzb_live_VOTRE_CLE" \
  -H "Content-Type: application/json" \
  -d '{ "priceCents": 49000 }'

# Désactiver (soft delete)
curl -X DELETE https://quizzbuy.com/api/v1/listings/ID_ANNONCE \
  -H "Authorization: Bearer zzb_live_VOTRE_CLE"

4. Click tracking

When a QuizzBuy visitor clicks through to your site, the landing URL contains a click identifier:

https://votre-site.fr/vendre?zzb_click=6f1e0c9a-3b2d-4e8f-9a10-abcdef123456

Store this value (cookie or session on your side, recommended duration: 45 days). It is what allows the order to be attributed. The parameter name (zzb_click by default) is configurable on request.

5. Notify an order (S2S postback)

As soon as a customer places an order (a purchase or a confirmed buyback request) and you have a zzb_click, call our postback from your server:

curl -X POST https://quizzbuy.com/api/v1/postback \
  -H "Authorization: Bearer zzb_live_VOTRE_CLE" \
  -H "Content-Type: application/json" \
  -d '{
    "click_id": "6f1e0c9a-3b2d-4e8f-9a10-abcdef123456",
    "type": "BUYBACK",
    "amount_cents": 52000,
    "currency": "EUR",
    "order_ref": "CMD-2026-000123"
  }'

# Réponse 201
{ "conversion_id": "…", "status": "TO_INVOICE", "commission_cents": 2600 }
  • Attribution window: 45 days after the click; beyond that, the response is 422 attribution_window_expired.
  • order_ref must be unique: a duplicate returns 409 duplicate_order_ref (idempotency — you can safely retry).
  • type: SALE for a purchase, BUYBACK for a buyback.
  • currency: EUR only — any other currency is rejected (422 unsupported_currency).

6. Test in sandbox

Add "test": true to the postback: the request is fully validated (key, click_id, 45-day window) but no conversion is recorded or billed.

{ "click_id": "…", "type": "SALE", "amount_cents": 10000, "order_ref": "TEST-1", "test": true }
# → 200 { "test": true, "valid": true, "commission_cents": 500 }

7. Error codes

CodeErrorExplanation
400invalid_inputInvalid request body (details in the response).
401unauthorizedAPI key missing, revoked, or invalid.
404click_not_found / not_foundUnknown click_id or listing (or not yours).
409duplicate_order_refOrder already notified.
422attribution_window_expiredClick older than 45 days.
429rate_limitedToo many requests — retry in a minute.

8. Billing

Each attributed conversion generates a commission (contractual rate, visible via /api/v1/me). QuizzBuy sends you a periodic summary invoice of validated conversions. For any question: contact us.

9. Marketplace — selling on QuizzBuy

SALE listings with a quantity > 0 are sold directly on QuizzBuy (customer payment handled by us, net of commission paid out to you). Additional fields: quantity (stock), color, grade (A/B/C), batteryHealth (%), warrantyMonths. The listing is automatically linked to the matching product page (brand + model + capacity + color).

# Mettre à jour le stock (sans re-modération)
curl -X PATCH https://quizzbuy.com/api/v1/listings/ID_ANNONCE/stock \
  -H "Authorization: Bearer zzb_live_VOTRE_CLE" \
  -H "Content-Type: application/json" \
  -d '{ "quantity": 12 }'

# Ajouter une photo (multipart, max 5 × 5 Mo, jpeg/png/webp)
curl -X POST https://quizzbuy.com/api/v1/listings/ID_ANNONCE/images \
  -H "Authorization: Bearer zzb_live_VOTRE_CLE" \
  -F "file=@photo.jpg"

10. Webhooks — get notified of sales

Register an HTTPS endpoint; we notify you at every stage of an order containing your products (order.created, order.paid, order.cancelled). The secret is only returned at creation — store it.

curl -X POST https://quizzbuy.com/api/v1/webhooks \
  -H "Authorization: Bearer zzb_live_VOTRE_CLE" \
  -H "Content-Type: application/json" \
  -d '{ "url": "https://votre-site.fr/webhooks/quizzbuy", "events": ["order.paid"] }'

# Réponse (secret affiché une seule fois)
{ "webhook": { "id": "…", "secret": "whsec_…", "events": ["order.paid"] } }

# Tester
curl -X POST https://quizzbuy.com/api/v1/webhooks/ID_WEBHOOK/test \
  -H "Authorization: Bearer zzb_live_VOTRE_CLE"

Each delivery is signed. Check the X-ZZbuy-Signature header (t=timestamp,v1=hex):

// Node.js
const [t, v1] = signature.split(",").map((p) => p.split("=")[1]);
const expected = crypto
  .createHmac("sha256", WEBHOOK_SECRET)
  .update(`${t}.${rawBody}`)
  .digest("hex");
const valid =
  crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1)) &&
  Math.abs(Date.now() / 1000 - Number(t)) < 300; // anti-replay 5 min

On failure (≠ 2xx), we retry with backoff: 1 min, 5 min, 30 min, 2 h, 12 h. You also receive a sale notification email.

11. Process your orders

List your order lines, acknowledge receipt, then ship with a tracking number (the customer is notified automatically). The delivery address is only visible after payment.

# Lister les commandes à traiter
curl "https://quizzbuy.com/api/v1/orders?status=PENDING" \
  -H "Authorization: Bearer zzb_live_VOTRE_CLE"

# Accuser réception
curl -X POST https://quizzbuy.com/api/v1/orders/ID_LIGNE/ack \
  -H "Authorization: Bearer zzb_live_VOTRE_CLE"

# Expédier
curl -X POST https://quizzbuy.com/api/v1/orders/ID_LIGNE/ship \
  -H "Authorization: Bearer zzb_live_VOTRE_CLE" \
  -H "Content-Type: application/json" \
  -d '{ "carrier": "Colissimo", "trackingNumber": "6A123456789FR" }'

12. Managed buyback (trade-in)

Managed buyback goes further than a simple redirect-based buyback: the individual submits their device from QuizzBuy, and you manage the entire case via the API (acceptance, receipt, counter-offer, payment). The price shown to the customer is locked at submission from your buyback price grid (see §14); you can only revise it downward after receipt, via a justified counter-offer.

All endpoints authenticate with your API key (Authorization: Bearer zzb_live_…) and only return your cases.

Lifecycle

SUBMITTEDACCEPTEDSHIPPEDRECEIVEDPAID. Two branches: REJECTED (declined before shipping) and COUNTER_OFFER (counter-offer after inspection, which the individual accepts — then PAID — or declines — device returned, CANCELLED).

StatusMeaning
SUBMITTEDRequest created by the individual, awaiting your decision.
ACCEPTEDYou have confirmed the buyback; the customer must ship the device.
SHIPPEDThe individual has provided their tracking number.
RECEIVEDYou have received the device; inspection in progress.
COUNTER_OFFERAfter inspection, you propose a revised (lower) amount.
PAIDPayment made to the individual — case closed.
REJECTEDRequest declined before shipping (not eligible, fraud…).
CANCELLEDCancelled (counter-offer declined, device returned).

List & retrieve

# Lister vos reprises (plus récentes d'abord)
curl "https://quizzbuy.com/api/v1/trade-ins?status=SUBMITTED&limit=50&offset=0" \
  -H "Authorization: Bearer zzb_live_VOTRE_CLE"

# Réponse
{
  "total": 3,
  "trade_ins": [
    {
      "trade_in_id": "…",
      "reference": "TI-2026-000042",
      "status": "SUBMITTED",
      "device": {
        "brand": "Apple", "category": "SMARTPHONE", "model": "iPhone 15 Pro",
        "storage": "256 GB", "condition": "GOOD",
        "functional_status": "FULLY_WORKING", "battery_health": 92, "imei": "…"
      },
      "offer_cents": 52000,
      "currency": "EUR",
      "price_locked_until": "2026-08-05T12:00:00.000Z",
      "country": "FR",
      "customer": { "email": "…", "first_name": "…", "last_name": "…", "locale": "fr" },
      "comment": null,
      "counter_offer_cents": null, "counter_reason": null,
      "tracking_carrier": null, "tracking_number": null,
      "shipping_label_url": null, "payout_ref": null,
      "accepted_at": null, "shipped_at": null, "received_at": null,
      "paid_at": null, "cancelled_at": null,
      "created_at": "2026-07-21T12:00:00.000Z"
    }
  ]
}

# Détail d'une reprise
curl https://quizzbuy.com/api/v1/trade-ins/ID_REPRISE \
  -H "Authorization: Bearer zzb_live_VOTRE_CLE"
# → { "trade_in": { … } }
  • status (optional filter): a value from the table above — otherwise 400 invalid_status.
  • limit: max 200 (default 50); offset for pagination.
  • The customer (individual's contact info) is only visible on this partner API, never in webhooks.

Transitions

Each action is a POST and returns { "ok": true, "trade_in": { … } }. A transition from an incompatible status returns 409 invalid_status (with the actual current). The individual is notified by email at each step.

# 1. Accepter (depuis SUBMITTED) — shippingLabelUrl facultatif (étiquette prépayée)
curl -X POST https://quizzbuy.com/api/v1/trade-ins/ID_REPRISE/accept \
  -H "Authorization: Bearer zzb_live_VOTRE_CLE" \
  -H "Content-Type: application/json" \
  -d '{ "shippingLabelUrl": "https://votre-site.fr/labels/ti-42.pdf" }'

# 2. Réceptionner l'appareil (depuis SHIPPED ou ACCEPTED)
curl -X POST https://quizzbuy.com/api/v1/trade-ins/ID_REPRISE/receive \
  -H "Authorization: Bearer zzb_live_VOTRE_CLE"

# 3a. Payer le montant garanti (depuis RECEIVED) — clôt la reprise
curl -X POST https://quizzbuy.com/api/v1/trade-ins/ID_REPRISE/pay \
  -H "Authorization: Bearer zzb_live_VOTRE_CLE" \
  -H "Content-Type: application/json" \
  -d '{ "payoutRef": "VIR-2026-000123" }'

# 3b. …ou contre-offrer après inspection (depuis RECEIVED) — montant < offre garantie
curl -X POST https://quizzbuy.com/api/v1/trade-ins/ID_REPRISE/counter \
  -H "Authorization: Bearer zzb_live_VOTRE_CLE" \
  -H "Content-Type: application/json" \
  -d '{ "amountCents": 42000, "reason": "Rayures écran non déclarées" }'

# Refuser avant envoi (depuis SUBMITTED uniquement)
curl -X POST https://quizzbuy.com/api/v1/trade-ins/ID_REPRISE/reject \
  -H "Authorization: Bearer zzb_live_VOTRE_CLE"
  • accept: only from SUBMITTED. shippingLabelUrl optional (URL, ≤ 500 chars).
  • receive: from SHIPPED or ACCEPTED (drop-off/shipping not declared by the customer).
  • counter: from RECEIVED. amountCents must be strictly lower than offer_cents, otherwise 400 counter_not_lower; reason required (1–500 chars). The individual then accepts or declines from their tracking page.
  • pay: from RECEIVED. payoutRef required (transfer reference, 1–120 chars).
  • reject: only from SUBMITTED — no body required.

13. Buyback webhooks (trade_in.*)

The same webhooks (§10, identical X-ZZbuy-Signature signature) cover managed buyback. Subscribe to all or some of the trade_in.* events when creating an endpoint:

curl -X POST https://quizzbuy.com/api/v1/webhooks \
  -H "Authorization: Bearer zzb_live_VOTRE_CLE" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://votre-site.fr/webhooks/quizzbuy",
    "events": ["trade_in.created", "trade_in.shipped", "trade_in.counter_accepted"]
  }'
EventTrigger
trade_in.createdNew request submitted by an individual (SUBMITTED status).
trade_in.shippedThe individual has provided their shipping tracking number.
trade_in.counter_acceptedThe individual has accepted your counter-offer.
trade_in.counter_declinedThe individual has declined your counter-offer (device returned).
trade_in.cancelledBuyback cancelled.

Delivery body (the individual's contact info is not included — retrieve it via GET /api/v1/trade-ins/:id):

{
  "trade_in_id": "…",
  "reference": "TI-2026-000042",
  "status": "SHIPPED",
  "device": {
    "brand": "Apple", "category": "SMARTPHONE", "model": "iPhone 15 Pro",
    "storage": "256 GB", "condition": "GOOD",
    "functional_status": "FULLY_WORKING", "battery_health": 92, "imei": "…"
  },
  "offer_cents": 52000,
  "currency": "EUR",
  "price_locked_until": "2026-08-05T12:00:00.000Z",
  "country": "FR"
}

14. Push your buyback price grid

A "push" alternative to our CSV pull flow: send your buyback price grid directly. Each row carries the 4 amounts according to device condition. Prices feed the same table as the automatic sync — so they appear immediately in the comparator and serve as the locked price for managed buyback (§12). The operation is an upsert by (brand, category, model, capacity).

curl -X PUT https://quizzbuy.com/api/v1/buyback/prices \
  -H "Authorization: Bearer zzb_live_VOTRE_CLE" \
  -H "Content-Type: application/json" \
  -d '{
    "prices": [
      {
        "brand": "Apple",
        "category": "SMARTPHONE",
        "model": "iPhone 15 Pro",
        "storage": "256 GB",
        "priceNewCents": 60000,
        "priceGoodCents": 52000,
        "priceFairCents": 40000,
        "priceBrokenCents": 15000,
        "currency": "EUR",
        "url": "https://votre-site.fr/reprise/iphone-15-pro"
      }
    ]
  }'

# Réponse
{ "ok": true, "upserted": 1 }
  • prices: 1 to 2000 rows per request.
  • category: SMARTPHONE, TABLET, LAPTOP, SMARTWATCH, AUDIO or GAMING.
  • priceNewCents (new), priceGoodCents (good), priceFairCents (fair), priceBrokenCents (broken) — in cents, ≥ 0.
  • storage and url optional; capacity is normalized automatically (e.g. « 256go » → « 256 GB »).