Documentazione partner

Tutto ciò che serve per pubblicare i tuoi annunci di vendita e ritiro su QuizzBuy e per notificarci gli ordini attribuiti al nostro traffico.

1. Ottenere una chiave API

Contatta il tuo referente QuizzBuy (o il modulo di contatto). Generiamo per te una chiave nel formato zzb_live_… — ti viene comunicata una sola volta, conservala in un luogo sicuro. Includila in ogni richiesta nell'header HTTP:

Authorization: Bearer zzb_live_VOTRE_CLE

2. Verificare la tua chiave

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. Pubblicare i tuoi annunci

Un annuncio è un'offerta di ritiro (BUYBACK — ritiri un dispositivo a un prezzo dato) oppure un'offerta di vendita (SALE — un prodotto ricondizionato che vendi). L'invio è un upsert: reinviare lo stesso externalId aggiorna l'annuncio.

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 o GAMING.
  • condition: LIKE_NEW, EXCELLENT, GOOD, FAIR o BROKEN.
  • priceCents: prezzo in centesimi (52000 = 520 €).
  • Ogni nuovo annuncio (o modifica) passa in moderazione prima della pubblicazione.

Aggiornare, elencare o disattivare:

# 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. Tracking dei clic

Quando un visitatore QuizzBuy clicca verso il tuo sito, l'URL di arrivo contiene un identificatore di clic:

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

Memorizza questo valore (cookie o sessione lato tuo sito, durata consigliata: 45 giorni). È questo valore che consentirà di attribuire l'ordine. Il nome del parametro (zzb_click di default) è configurabile su richiesta.

5. Notificare un ordine (postback S2S)

Non appena un cliente effettua un ordine (acquisto o richiesta di ritiro convalidata) e disponi di uno zzb_click, chiama il nostro postback dal tuo 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 }
  • Finestra di attribuzione: 45 giorni dopo il clic; oltre, risposta 422 attribution_window_expired.
  • order_ref deve essere univoco: un duplicato restituisce 409 duplicate_order_ref (idempotenza — puoi riprovare senza rischi).
  • type: SALE per un acquisto, BUYBACK per un ritiro.
  • currency: solo EUR — qualsiasi altra valuta viene rifiutata (422 unsupported_currency).

6. Testare in sandbox

Aggiungi "test": true al postback: la richiesta viene interamente validata (chiave, click_id, finestra di 45 giorni) ma nessuna conversione viene registrata o fatturata.

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

7. Codici di errore

CodiceErroreSpiegazione
400invalid_inputCorpo della richiesta non valido (dettagli nella risposta).
401unauthorizedChiave API assente, revocata o non valida.
404click_not_found / not_foundclick_id o annuncio sconosciuto (o non tuo).
409duplicate_order_refOrdine già notificato.
422attribution_window_expiredClic risalente a più di 45 giorni fa.
429rate_limitedTroppe richieste — riprova tra un minuto.

8. Fatturazione

Ogni conversione attribuita genera una commissione (tasso contrattuale, visibile tramite /api/v1/me). QuizzBuy ti invia una fattura riepilogativa periodica delle conversioni convalidate. Per qualsiasi domanda: contattaci.

9. Marketplace — vendere su QuizzBuy

Gli annunci SALE con una quantity > 0 vengono venduti direttamente su QuizzBuy (pagamento del cliente presso di noi, riversamento del netto di commissione). Campi aggiuntivi: quantity (stock), color, grade (A/B/C), batteryHealth (%), warrantyMonths. L'annuncio viene collegato automaticamente alla scheda prodotto corrispondente (marca + modello + capacità + colore).

# 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. Webhook — essere avvisati delle vendite

Registra un endpoint HTTPS; ti notifichiamo a ogni fase di un ordine contenente i tuoi prodotti (order.created, order.paid, order.cancelled). Il secret viene restituito solo alla creazione — memorizzalo.

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"

Ogni consegna è firmata. Verifica l'header X-ZZbuy-Signature (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

In caso di errore (≠ 2xx), riproviamo con backoff: 1 min, 5 min, 30 min, 2 h, 12 h. Ricevi anche un'email di notifica di vendita.

11. Gestire i tuoi ordini

Elenca le righe dei tuoi ordini, conferma la ricezione e poi spedisci con un numero di tracking (il cliente viene notificato automaticamente). L'indirizzo di consegna è visibile solo dopo il pagamento.

# 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. Reprise gestita (trade-in)

La reprise gestita va oltre il semplice ritiro tramite reindirizzamento: il privato invia il proprio dispositivo da QuizzBuy, e tu gestisci l'intera pratica tramite l'API (accettazione, ricezione, controproposta, pagamento). Il prezzo mostrato al cliente è bloccato al momento dell'invio in base alla tua griglia di ritiro (vedi §14); puoi rivederlo al ribasso solo dopo la ricezione tramite una controproposta motivata.

Tutti gli endpoint si autenticano con la tua chiave API (Authorization: Bearer zzb_live_…) e restituiscono solo le tue pratiche.

Ciclo di vita

SUBMITTEDACCEPTEDSHIPPEDRECEIVEDPAID. Due diramazioni: REJECTED (rifiuto prima dell'invio) e COUNTER_OFFER (controproposta dopo l'ispezione, che il privato accetta — poi PAID — o rifiuta — restituzione del dispositivo, CANCELLED).

StatoSignificato
SUBMITTEDRichiesta creata dal privato, in attesa della tua decisione.
ACCEPTEDHai confermato il ritiro; il cliente deve spedire il dispositivo.
SHIPPEDIl privato ha inserito il proprio numero di tracking.
RECEIVEDHai ricevuto il dispositivo; ispezione in corso.
COUNTER_OFFERDopo l'ispezione, proponi un importo rivisto (inferiore).
PAIDPagamento effettuato al privato — pratica chiusa.
REJECTEDRichiesta rifiutata prima dell'invio (non idoneo, frode…).
CANCELLEDAnnullata (rifiuto di una controproposta, restituzione del dispositivo).

Elencare e consultare

# 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 (filtro facoltativo): un valore della tabella qui sopra — altrimenti 400 invalid_status.
  • limit: max 200 (default 50); offset per la paginazione.
  • Il customer (contatto del privato) è visibile solo tramite questa API partner, mai nei webhook.

Transizioni

Ogni azione è un POST e restituisce { "ok": true, "trade_in": { … } }. Una transizione da uno stato incompatibile restituisce 409 invalid_status (con lo stato current reale). Il privato viene notificato via email a ogni fase.

# 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: solo da SUBMITTED. shippingLabelUrl facoltativo (URL, ≤ 500 car.).
  • receive: da SHIPPED o ACCEPTED (deposito/invio non dichiarato dal cliente).
  • counter: da RECEIVED. amountCents deve essere rigorosamente inferiore a offer_cents, altrimenti 400 counter_not_lower; reason obbligatorio (1–500 car.). È poi il privato che accetta o rifiuta dalla propria pagina di tracking.
  • pay: da RECEIVED. payoutRef obbligatorio (riferimento del bonifico, 1–120 car.).
  • reject: solo da SUBMITTED — nessun corpo richiesto.

13. Webhook reprise (trade_in.*)

Gli stessi webhook (§10, firma X-ZZbuy-Signature identica) coprono la reprise gestita. Iscriviti a tutti o parte degli eventi trade_in.* alla creazione di un 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"]
  }'
EventoTrigger
trade_in.createdNuova richiesta inviata da un privato (stato SUBMITTED).
trade_in.shippedIl privato ha inserito il proprio tracking di spedizione.
trade_in.counter_acceptedIl privato ha accettato la tua controproposta.
trade_in.counter_declinedIl privato ha rifiutato la tua controproposta (restituzione del dispositivo).
trade_in.cancelledReprise annullata.

Corpo della consegna (il contatto del privato non vi figura — recuperalo tramite 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. Inviare la tua griglia di ritiro

Alternativa «push» al flusso CSV importato da noi: invia direttamente la tua griglia di prezzi di ritiro. Ogni riga riporta i 4 importi in base allo stato del dispositivo. I prezzi alimentano la stessa tabella della sincronizzazione automatica — quindi appaiono immediatamente nel comparatore e fungono da prezzo bloccato per la reprise gestita (§12). L'operazione è un upsert per (marca, categoria, modello, capacità).

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: da 1 a 2000 righe per richiesta.
  • category: SMARTPHONE, TABLET, LAPTOP, SMARTWATCH, AUDIO o GAMING.
  • priceNewCents (nuovo), priceGoodCents (buono), priceFairCents (segnato), priceBrokenCents (rotto) — in centesimi, ≥ 0.
  • storage e url facoltativi; la capacità viene normalizzata automaticamente (es. «256go» → «256 GB»).