Introduzione
Bozza — l'invio degli eventi non è ancora aperto. Questa specifica
è pubblicata per essere valutata e commentata dai corrieri.
POST /v1/events non accetta ancora traffico: risponde 404
finché non lo attiviamo per il vostro codice corriere.
POST /v1/events/validate invece è attivo, in produzione.
Dall'attivazione in poi — è il passaggio che vi assegna URL,
token e segreto di firma — potete validare i vostri payload contro
lo schema senza aspettare altro. Prima di pianificare il resto del lavoro, concordare i
tempi con tech@qapla.it.
Scopo
Un contratto unico, indipendente dal corriere, per inviare a Qapla' gli eventi di tracking delle spedizioni.
Se ci avete chiesto un URL webhook a cui inviare gli eventi di tracking, questa è la specifica di quel webhook: cosa mandare, in che forma, e come rispondiamo. L'URL non è pubblico e non è unico per tutti — ne assegniamo uno a voi insieme alle credenziali, al primo passo dell'attivazione.
Qapla' traccia le spedizioni di migliaia di merchant europei. Storicamente lo stato lo andiamo a leggere noi, interrogando l'API di ogni corriere: autenticazioni diverse, limiti di polling diversi, payload diversi, e un ritardo fra l'evento reale e la notifica che arriva al destinatario.
Questa specifica ribalta il verso: il corriere invia, noi ascoltiamo. Un endpoint, una forma JSON, una politica di retry. Toglie carico di polling dall'infrastruttura del corriere e porta l'evento al destinatario finale in secondi invece che in decine di minuti.
La regola di progetto dietro ogni scelta che segue: il corriere non deve cambiare il modo in cui pensa i propri dati. Invia i propri codici di stato, con le proprie parole, come flusso di fatti in sola aggiunta. La normalizzazione è compito nostro, non suo.
Endpoint
Assegniamo un URL in fase di attivazione. Gli URL non sono elencati qui: sono per corriere, e comunicarli fuori dal percorso di attivazione non avrebbe senso.
Oggi l'ambiente è uno solo. Non c'è una sandbox separata, e per
sviluppare non serve: /v1/events/validate non salva nulla,
quindi si può chiamare quanto si vuole senza produrre alcun effetto.
Quello che è stabile è la forma dei path sotto la base assegnata —
https://{endpoint-host} qui sotto sta per quella:
| Path | Scopo |
|---|---|
| POSThttps://{endpoint-host}/v1/events | invio degli eventi |
| POSThttps://{endpoint-host}/v1/events/validate | validazione di un payload, senza salvare nulla. Attivo: usarlo liberamente in sviluppo e nella propria CI |
| GEThttps://{endpoint-host}/v1/ping | verifica delle credenziali. Risponde 200 con il codice corriere assegnato. Nessun effetto collaterale |
Tenere l'URL configurabile, non compilato nel codice. La nostra piattaforma di ingestione è serverless (Google Cloud, europe-west1) e l'URL può cambiare per ragioni operative nostre: migrazione di regione, isolamento del traffico di un singolo corriere, ridimensionamento. Diamo preavviso e una finestra di sovrapposizione durante la quale sia il vecchio sia il nuovo URL rispondono, ma serve che dalla vostra parte sia un parametro di configurazione, non una costante da rilasciare.
Per le vostre regole di uscita: la destinazione è nello spazio di indirizzi di Google Cloud, regione europe-west1, sempre HTTPS sulla porta 443. Non esponiamo un indirizzo IP fisso su cui fare allowlist. Se la vostra policy richiede un hostname o un IP stabile, segnalatelo in fase di attivazione: è gestibile, ma va previsto prima.
Content-Type: application/json, UTF-8. Content-Encoding: gzip è
supportato e raccomandato per i batch.
Autenticazione
Obbligatoria — bearer token
POST /v1/events HTTP/1.1
Host: {endpoint-host}
Authorization: Bearer <token>
Content-Type: application/json
Qapla' emette un token per corriere e per ambiente. Il token identifica il mittente; non scade a scadenza fissa e si può ruotare su richiesta, con una finestra di sovrapposizione durante la quale sono accettati sia il vecchio sia il nuovo.
Opzionale — firma del payload
Per non affidarsi al solo bearer token, aggiungere:
X-Qapla-Signature: t=1787654400,v1=5257a869e7ecebeda32affa62cdca3fa793333cd8b3f7a0d3d9ed3c1f0b1b1ab
v1 è l'HMAC-SHA256 in esadecimale di "<t>.<corpo grezzo della
richiesta>", con chiave il segreto condiviso che emettiamo insieme al token.
Rifiutiamo una firma il cui t si discosti di più di 5 minuti dal nostro
orologio. Se l'header c'è lo verifichiamo, se non c'è accettiamo la richiesta sul solo bearer
token. Su richiesta la firma può diventare obbligatoria per il singolo codice corriere.
Con Content-Encoding: gzip si firma il corpo compresso.
Il corpo grezzo sono i byte come viaggiano sul filo: grezzo vuol dire prima di
qualsiasi decodifica, non prima di qualsiasi codifica. Prima si comprime, poi si
calcola l'HMAC su quello che si sta per trasmettere.
Firmando il corpo trasmesso l'autenticazione avviene prima della decompressione. L'altra lettura ci obbligherebbe a decomprimere un payload non ancora autenticato per poterlo autenticare, cioè a far girare il decompressore sull'input di chiunque conosca la URL. È anche quello che fanno Stripe e GitHub.
Opzionale — allowlist di IP
Comunicandoci i propri range di uscita, accetteremo traffico solo da quelli.
Non richiesti: OAuth2 client-credentials e mTLS. Entrambi sono disponibili se la vostra policy di sicurezza li impone — è sufficiente segnalarlo e li attiviamo per il vostro codice corriere — ma nessuno dei due è una precondizione, e non riteniamo che aggiungano molto a un invio unidirezionale server-to-server su TLS 1.2+.
Eventi
POST/v1/events
Un messaggio è una busta che trasporta una lista piatta di eventi. Un evento è un fatto, su un collo, in un istante. Non esistono uno "stato corrente" e uno "storico" separati: l'evento più recente è lo stato corrente.
Si può inviare la storia intera, solo ciò che è cambiato dall'ultima chiamata, o eventi fuori ordine: sono tutti e tre validi, perché ogni evento porta con sé la propria identità e il proprio istante.
POSThttps://{endpoint-host}/v1/events
La chiamata completa, senza niente di implicito:
curl -X POST https://{endpoint-host}/v1/events \
-H "Authorization: Bearer $QAPLA_TOKEN" \
-H "Content-Type: application/json" \
--data-binary @event.json
POST/v1/events/validate
Identico a /v1/events per autenticazione e payload, ma non salva
nulla: valida il messaggio contro lo schema e restituisce gli stessi
results[], con outcome ACCEPTED per gli eventi che
sarebbero stati accettati.
POSThttps://{endpoint-host}/v1/events/validate
È il punto da cui conviene partire: è attivo già ora, prima che l'invio degli eventi sia aperto, non richiede che le spedizioni esistano già da noi, e si può tenere nella propria pipeline di CI come test di non-regressione del payload.
GET/v1/ping
GEThttps://{endpoint-host}/v1/ping
{
"courier": "ACME_EXPRESS",
"environment": "production",
"schemaVersions": ["1.0"]
}
Payload
Struttura della busta
| Campo | Descrizione |
|---|---|
| schemaVersion*(string) | versione dello schema, "1.0". Vedi Versioning |
| messageId*(string, max 128) | univoco per chiamata HTTP. Nel retry della stessa chiamata va
reinviato lo stesso valore. Suggeriamo un UUID, ma va bene qualunque
identificativo stabile: non imponiamo un formato. Serve alla tracciabilità:
non deduplichiamo sul messageId, ma
sull'eventId. Rimettere un evento in un messaggio nuovo, con un
messageId nuovo, è ammesso purché l'eventId resti lo
stesso |
| sentAt*(string, date-time) | quando il messaggio è stato composto |
| courier*(string) | il codice corriere che Qapla' assegna in fase di attivazione. Deve corrispondere al token |
| events*(array) | da 1 a 500 elementi |
* Campo obbligatorio
Campi sconosciuti: tollerati sulla busta, rifiutati sull'evento. Un
campo vostro accanto a courier o sentAt viene ignorato — un
batch di 500 eventi non si perde per un campo che non ci aspettavamo. Dentro l'evento
vale l'opposto: un campo sconosciuto rende INVALID quell'evento, ed
è voluto, perché attributes è il posto per tutto ciò che è vostro e non ha un
campo proprio.
Campi dell'evento — obbligatori
| Campo | Descrizione |
|---|---|
| eventId*(string, max 128) | Identità stabile e permanente dell'evento nei sistemi del corriere. È la chiave di idempotenza: se lo vediamo due volte, scartiamo la seconda copia. Deve essere lo stesso valore ogni volta che l'evento viene inviato, retry e reinvii della storia completa compresi. L'id di riga del proprio registro eventi è la scelta ideale |
| trackingNumber(string, max 64) | il numero che conosce il destinatario. Obbligatorio questo oppure
externalId — vedi Riferimento
alternativo. Se cambia, vedi Cambio tracking number |
| occurredAt*(string, date-time) | quando l'evento è avvenuto nel mondo reale, non quando è
stato inviato. ISO 8601 con offset esplicito, nell'ora locale del luogo
in cui l'evento è avvenuto: 2026-08-24T10:28:00+02:00. Mai
il fuso del proprio datacenter, mai un timestamp senza fuso. Z è
accettato ma perde la lettura locale — ed è la lettura locale quella che viene
mostrata al destinatario. Il JSON Schema lo impone con
un'espressione regolare, non solo con format: un timestamp senza
fuso fallisce la validazione nella vostra pipeline, che è dove correggerlo
costa poco |
| status.code*(string, max 64) | il proprio codice nativo, alla lettera. Da non tradurre in un vocabolario normalizzato — vedi Codici di stato |
| status.description*(string, max 500) | il proprio testo leggibile, nella lingua del paese dell'evento se disponibile |
Campi dell'evento — opzionali
Tutti realmente opzionali: inviare ciò che si ha.
| Campo | Descrizione |
|---|---|
| externalId(string, max 128) | identificativo alternativo del collo, tipicamente il riferimento ordine del merchant. Obbligatorio se manca trackingNumber |
| senderAccount(string, max 64) | il codice conto, contratto o cliente sotto cui la spedizione è stata prenotata. Obbligatorio quando si invia externalId senza trackingNumber — vedi Riferimento alternativo. Gradito sempre |
| location.name(string) | nome della filiale, città o hub |
| location.countryCode(string, 2) | ISO 3166-1 alpha-2 |
| location.postalCode(string) | |
| parcel.trackingNumber(string) | numero del singolo collo in una spedizione multi-collo |
| parcel.index / parcel.total(integer) | collo 2 di 3 |
| reason.code / reason.description(string) | perché si è verificata l'eccezione o il tentativo fallito. È il più utile fra i campi opzionali: è ciò che permette al merchant di agire prima che il destinatario si lamenti |
| delivery.estimatedDeliveryDate(string, date) | giorno di consegna previsto, nel fuso del luogo di consegna. Vedi Consegna prevista |
| delivery.estimatedDeliveryWindow.from / .to(string, date-time) | finestra ristretta, quando è nota. Vedi Consegna prevista |
| delivery.deliveredTo(string) | RECIPIENT, NEIGHBOUR, MAILBOX, PICKUP_POINT, SAFE_PLACE, OTHER |
| delivery.signedBy(string) | nome sulla prova di consegna |
| delivery.podUrl(string, URI) | link alla prova di consegna |
| delivery.attemptNumber(integer) | numero del tentativo, da 1 |
| pickupPoint.id / .name / .address / .postalCode / .countryCode(string) | dove il collo è in attesa, quando l'evento lo deposita in un locker o in un punto di ritiro |
| pickupPoint.availableUntil(string, date-time) | |
| trackingNumberChange.newTrackingNumber / .reason(string) | vedi Cambio tracking number |
| recipientActionUrl(string, URI) | la propria pagina dove il destinatario può riprogrammare, dirottare o autorizzare il deposito. La esponiamo al destinatario |
| isReturn(boolean) | true se il collo sta tornando al merchant. Default false |
| attributes(object) | mappa piatta di valori stringa per qualunque dato specifico del corriere che non abbia un campo dedicato. Da usare al posto di chiedere una modifica dello schema. Massimo 20 chiavi |
Cambio tracking number
Alcuni colli ricevono un identificativo provvisorio alla creazione dell'etichetta e il
tracking number definitivo solo più tardi; altri vengono riassegnati durante il viaggio. I
due casi sono lo stesso evento: inviarlo sotto il numero che il destinatario conosce
adesso, e mettere il nuovo in trackingNumberChange.
Dall'evento successivo, usare il nuovo numero come trackingNumber.
Riferimento alternativo
Alcuni flussi di eventi non sono indicizzati sul tracking number ma sul riferimento
ordine del merchant. In quel caso, inviare quel valore come externalId insieme a
senderAccount — il codice conto, contratto o cliente sotto cui la spedizione è
stata prenotata.
Almeno uno fra trackingNumber ed externalId deve essere
presente. Inviarli entrambi quando si hanno entrambi: è l'input più
affidabile che possiamo ricevere, perché ci permette di agganciare il collo in due modi e di
accorgerci di un disallineamento.
Senza trackingNumber, senderAccount è
obbligatorio — e non è burocrazia. Il riferimento ordine lo genera il vostro
cliente, non voi: due merchant che spediscono con voi possono chiamare entrambi un
ordine ORD-1, e non c'è niente che voi o noi possiamo fare per rendere unico
quel valore. Il codice conto è ciò che dice di quale dei due stiamo parlando. Senza, non
possiamo distinguerli, e un evento di consegna sul collo sbagliato è un'email alla
persona sbagliata — quindi preferiamo rispondere UNKNOWN_TRACKING piuttosto
che tirare a indovinare. Ogni volta che più di una spedizione corrisponde, è esattamente
quello che facciamo.
Consegna prevista
La consegna prevista si invia dentro delivery, agganciata a un evento come
ogni altra informazione: estimatedDeliveryDate per il giorno,
estimatedDeliveryWindow per la finestra oraria quando esiste. Entrambi sono
opzionali e possono comparire insieme.
Non esiste un endpoint per aggiornarla. Una stima cambia — il collo perde
una tratta, il giro del giorno slitta — e il modo per rivederla è emettere un evento
nuovo che porti il valore aggiornato. Vale come stima corrente quella dell'evento
più recente che la contiene: un evento senza delivery non cancella la
stima precedente, la lascia in piedi.
Ne segue una conseguenza pratica: se la stima cambia e non c'è nessun avanzamento di stato
da comunicare, va comunque inviato un evento — con il proprio codice di stato corrente,
eventId nuovo e delivery aggiornato. È il solo modo che abbiamo di
vedere il cambiamento, ed è il caso in cui il destinatario ci tiene di più.
La consegna prevista arriva al destinatario: finisce nelle notifiche transazionali e nella pagina di tracking. Una stima ottimistica non ritrattata è il reclamo più comune che i nostri merchant ricevono — meglio nessuna stima che una stima ferma.
Codici di stato: i vostri, non i nostri
Deliberatamente non pubblichiamo un elenco di valori su cui mappare.
Il vocabolario di stati di ogni corriere è più fine di qualunque elenco condiviso, e
imporre una mappatura alla sorgente distrugge esattamente il dettaglio che rende utile
l'evento: "tentativo di consegna, destinatario assente" e "tentativo di consegna, indirizzo
non trovato" collassano entrambi in EXCEPTION, e il merchant perde la
possibilità di reagire in modo diverso. Peggio: quella mappatura finirebbe nel vostro codice,
dove non possiamo correggerla quando si rivela sbagliata.
Quindi: inviare status.code esattamente come appare nei propri sistemi.
Qapla' mantiene la mappatura dai vostri codici ai propri stati normalizzati, e si assume le
conseguenze di sbagliarla.
Cosa ci serve da voi, una volta sola, in fase di attivazione: il catalogo dei codici di stato — codice, descrizione e, se la nozione esiste, se il codice è terminale. CSV, JSON o una pagina della vostra documentazione vanno benissimo.
I codici che compaiono nel traffico senza essere in catalogo vengono accettati, memorizzati e segnalati a noi per la mappatura: semplicemente non producono una notifica al destinatario finché non li mappiamo.
Risposte
Esito per evento — 200 OK
Rispondiamo per evento. Un batch non viene mai rifiutato in blocco perché uno dei suoi eventi non era utilizzabile.
| Campo | Descrizione |
|---|---|
| messageId(string) | eco del messageId inviato |
| receivedAt(string, date-time) | quando abbiamo ricevuto il messaggio |
| accepted / duplicate / unknownTracking / invalid(integer) | un contatore per outcome: i quattro sommano sempre al numero di
eventi inviati. Non esiste volutamente un contatore rejected unico:
solo invalid è un problema su cui si può agire |
| results(array) | un elemento per evento, nell'ordine di invio: eventId,
outcome, e errors[] quando l'esito è
INVALID |
Se si mette un allarme su un numero, mettetelo su invalid.
Non su unknownTracking: se ci inviate tutto il flusso, quel contatore è la
maggior parte del traffico anche in una giornata perfettamente sana, e allarmarsi su
quello significa svegliare qualcuno ogni minuto dal primo giorno.
| outcome | Significato | Ritentare? |
|---|---|---|
| ACCEPTED | memorizzato e in elaborazione | no |
| DUPLICATE | questo eventId è già noto. Non è un errore |
no |
| UNKNOWN_TRACKING | non tracciamo questo collo — appartiene a un merchant che non è nostro cliente. È previsto e innocuo: se inviate l'intero flusso di eventi, la maggior parte tornerà così | no |
| INVALID | l'evento viola lo schema. Correggerlo e reinviarlo con lo stesso
eventId |
no |
Perché sempre 200 e mai 201 per una prima
scrittura? Un batch è normalmente misto — qualche evento nuovo, qualcuno già
visto, qualcuno di colli che non tracciamo — e un solo status line non può esprimerlo.
ACCEPTED contro DUPLICATE in results[] porta la
stessa informazione per evento, e in modo più preciso. Lo status HTTP dice "il messaggio
è stato elaborato"; results[] dice "che cosa è successo a ciascun
evento".
Che fine fa un evento UNKNOWN_TRACKING. Non raggiunge
nessun merchant e nessun destinatario, e non lo conserviamo come dato di tracking di un
collo che non è nostro da tracciare.
Lo mettiamo però da parte per 7 giorni, e
non è un dettaglio implementativo: molti merchant ci dichiarano le spedizioni a fine
giornata o la mattina dopo, quindi i vostri primi eventi — accettazione, presa in carico,
primo transito — arrivano regolarmente prima che la spedizione esista da noi.
Non appena esiste, gli eventi messi da parte si applicano insieme al primo evento che la
trova, in ordine di occurredAt, come se li avessimo abbinati subito. Non dovete
rimandarli: UNKNOWN_TRACKING resta "non ritentare".
Da parte teniamo solo ciò che serve a ricostruire il tracking — identificativi,
status, occurredAt, location.name — e scartiamo
subito i campi che riguardano una persona: delivery.signedBy,
delivery.podUrl, l'indirizzo del punto di ritiro,
recipientActionUrl. Passati i 7 giorni non resta
niente. Se potete filtrare il flusso sui merchant che sapete usare Qapla', è gradito — ma
non è una cosa che vi chiediamo di costruire.
Errori a livello di messaggio
| Codice | Significato | Ritentare? |
|---|---|---|
| 400 | il corpo non è JSON, o la busta non è valida | no |
| 401 | bearer token assente o sconosciuto | no |
| 403 | firma non valida, IP di origine non ammesso, o courier che non corrisponde al token | no |
| 404 | /v1/events non accetta ancora traffico per il vostro codice corriere (vedi Attivazione) | no |
| 413 | payload troppo grande (vedi Limiti) | no — dividerlo |
| 415 | Content-Type errato | no |
| 429 | rate limit superato. Rispettare Retry-After | sì |
| 5xx | problema nostro | sì |
Il corpo di ogni 4xx è un problem document
RFC 7807.
Il 404 è deliberato, e non un 501 o un 503:
quelli sono 5xx, e la retry policy pubblicata qui dice
di ritentare sui 5xx — un corriere che partisse prima dell'attivazione
finirebbe in un loop di tentativi. Il 404 lo ferma, e basta.
Retry
Ritentare su 429, su 5xx, sui fallimenti di connessione e sui
timeout. Backoff esponenziale con jitter, almeno 5 tentativi distribuiti su circa 15
minuti (per esempio 10s → 30s → 2m → 5m → 10m).
Tre tentativi in pochi secondi — default frequente — non bastano: i nostri deploy e i
failover possono rendere l'endpoint indisponibile più a lungo, e un evento abbandonato
dopo 7 secondi è un evento che il destinatario non vedrà mai. Conservare i messaggi non
recapitabili in una dead-letter queue e rigiocarli: riconosciamo un eventId
già visto come duplicato per almeno 6 mesi, quindi un replay
dentro quella finestra è gratis. Oltre, avvisateci prima di rigiocare.
Timeout di lettura: 10 secondi. In condizioni normali rispondiamo ampiamente sotto il
secondo. Un timeout non è una mancata consegna: gli eventi potrebbero essere
già stati memorizzati. Ritentare con lo stesso messageId e gli stessi
eventId è la risposta corretta e sicura. Chi gestisce il retry per singolo
evento può anche reinserirlo in un batch successivo: conta l'eventId, non il
messageId.
Riferimento
Versioning
schemaVersion è un campo di primo livello, major.minor.
- Minor: nuovi campi opzionali, nuovi valori di
outcome. Retrocompatibile — lo rilasciamo e la vostra integrazione continua a funzionare senza modifiche. Ignorare i campi che non si conoscono. - Major: qualunque cosa possa rompere un mittente. Annunciata con almeno 90 giorni di preavviso, e la major precedente resta accettata per almeno 12 mesi dall'annuncio.
Non rimuoviamo né riusiamo per altro un campo dentro la stessa major.
Limiti
| Eventi per messaggio | 500 |
|---|---|
| Corpo non compresso | 5 MiB |
| Rate limit | 20 richieste/secondo sostenute, burst 100 — innalzabile su richiesta, comunicandoci il volume previsto |
| Timeout di lettura | 10 s |
X-RateLimit-Limit e X-RateLimit-Remaining sono restituiti su ogni
risposta.
Attivazione
- Ci comunicate il volume giornaliero di eventi previsto e il picco, e ci inviate
il vostro logo in formato vettoriale (SVG, sfondo trasparente, senza
margini): lo usiamo nelle email di tracking e nella pagina di tracking pubblica. Vi
assegniamo l'URL del vostro endpoint, un token, un segreto di firma e
il vostro codice
courier. - Ci inviate il vostro catalogo dei codici di stato.
- Sviluppate contro
POST /v1/events/validate, con i vostri tempi. Lo JSON Schema e gli esempi sono gli artefatti normativi: far girare un validatore contro di essi è il modo più rapido per sapere di avere finito. - Attiviamo
/v1/eventsper il vostro codice corriere e confrontiamo il primo giorno di traffico reale con il tracking che già raccogliamo sugli stessi colli: è il passaggio che intercetta errori di mappatura e di fuso orario prima che raggiungano un destinatario. - Passaggio a regime. Potete tenere in parallelo la vostra integrazione
pull attuale quanto volete: grazie a
eventIdgli eventi duplicati non costano nulla.
Schema ed esempi
| File | Contenuto |
|---|---|
| courier-events.postman_collection.json | collection Postman: /v1/ping, /v1/events/validate e
/v1/events, più ogni esempio pronto da eseguire. Generata dagli
esempi qui sotto |
| courier-events-v1.schema.json | JSON Schema 2020-12 della busta. Artefatto normativo |
| 01-minimal.json | il messaggio più piccolo valido |
| 02-full-history-batch.json | storia completa di una spedizione in un solo messaggio |
| 03-failed-attempt-and-pickup-point.json | tentativo fallito con reason, poi deposito in punto di ritiro |
| 04-tracking-number-change.json | assegnazione del tracking number definitivo |
| 05-return-shipment.json | reso verso il merchant |
| 06-external-id-only.json | eventi indicizzati sul riferimento ordine |
| 90-response-200.json | risposta con esito per evento |
| 91-response-403.json | problem document RFC 7807 |
La collection arriva con le variabili vuote, ed è voluto. Dalla
scheda Variables della collection compilate endpoint_host — l'host
del vostro endpoint, senza https://, quello che vi assegniamo
all'attivazione — e token. Poi partite da
GET /v1/ping: una URL sbagliata o un token sbagliato si scoprono lì,
invece che dentro un payload dove sembrerebbero un problema di schema.
Una volta compilate, non riesportate il file: l'export conterrebbe il
vostro token. E la firma opzionale X-Qapla-Signature non è nella
collection, perché è un HMAC-SHA256 da ricalcolare a ogni richiesta e una collection
statica non può farlo; le richieste si autenticano col solo bearer token, che è quello
che accettiamo quando l'header non c'è.
Domande che questa specifica non risolve
Le lasciamo qui apposta, perché vale la pena deciderle insieme invece di darle per scontate:
- Volete un canale di riscontro? Possiamo esporre una callback firmata che vi dica quali eventi hanno prodotto una notifica al destinatario. Nessuno l'ha ancora chiesta.
- Volete che siamo noi a inviare verso di voi? Alcuni corrieri preferiscono ricevere l'istruzione di riconsegna come chiamata API invece che come clic del destinatario. Fuori perimetro per la v1.
- Identità a livello di spedizione o di collo. La v1 indicizza tutto sul tracking number che conosce il destinatario. Se il vostro modello è per spedizione con i colli sotto, segnalatelo: lo valutiamo prima di congelare la v1.
Contatto: tech@qapla.it