Sari la conținut

Webhook-uri către clienții 3PL ​

notso WMS poate anunța sistemele unui client 3PL despre propriile lui mărfuri: o comandă expediată (order.shipped), anulată (order.cancelled), livrată (order.delivered), pusă în așteptare (order.held) sau scoasă din așteptare (order.released) ori o recepție finalizată (receiving.completed). Endpointurile se configurează la Clienți → Webhook-uri, per client. Fiecare livrare este o cerere POST cu corp JSON, semnată cu un secret cunoscut doar de client și de WMS.

Această pagină descrie ce trimite WMS, cum verificați semnătura și ce reguli se aplică adresei destinației.

Corpul livrării ​

Corpul este un obiect JSON cu cinci câmpuri, în această ordine: event, event_id, occurred_at, sent_at și data. Câmpul data conține doar datele clientului căruia îi aparține endpointul.

json
{
    "event": "order.shipped",
    "event_id": "3f2b8c1e-6a4d-4e0b-9c57-1d2e3f4a5b6c",
    "occurred_at": "2026-10-12T09:15:00+03:00",
    "sent_at": "2026-10-12T09:15:02+03:00",
    "data": { /* depinde de eveniment */ }
}
  • event_id (UUID) identifică evenimentul din afaceri și este același la fiecare încercare, la fiecare reîncercare și la fiecare retrimitere, pentru toate endpointurile clientului. Același identificator este trimis și în antetul X-Wms-Event-Id.
  • occurred_at este momentul în care s-a petrecut faptul: expedierea (order.shipped), finalizarea recepției (receiving.completed), decizia asupra cererii de retur (return.authorized, return.rejected), momentul în care returul a fost asociat cererii (return.received), momentul în care unitățile au fost propuse clientului (disposition.awaiting_decision), momentul în care a fost ridicată alerta (stock.low, stock.expiring), momentul anulării și momentul livrării comenzii (order.cancelled, order.delivered), momentul în care așteptarea a devenit activă (order.held) și momentul în care s-a încheiat (order.released). Nu se schimbă între încercări.
  • sent_at este momentul acestei încercări și se schimbă la fiecare încercare.

La apăsarea butonului Testează, WMS trimite o livrare cu evenimentul webhook.test, semnată la fel ca una reală.

Evenimentul receiving.completed ​

Se trimite când depozitul finalizează o recepție. Câmpul data conține, în această ordine: session_id, session_number, reference (documentul furnizorului, sau null), completed_at, asn_id, asn_reference și lines.

  • asn_id și asn_reference numesc avizul de expediție (ASN) al clientului pe care s-a făcut recepția; sunt null când recepția nu a pornit de la un aviz.
  • Fiecare linie conține sku, received_quantity, batch_number, accepted_quantity și damaged_quantity. accepted_quantity este cantitatea bună, primită în stoc (inclusiv marfa reținută pe care depozitul a eliberat-o după aprobare); damaged_quantity este marfa deteriorată sau expirată încă reținută. Marfa respinsă și marfa bună încă în așteptarea aprobării fac parte din received_quantity, dar nu și din celelalte două cifre.
json
{
    "event": "receiving.completed",
    "event_id": "9a1d7e52-0c3b-4f68-8d21-5b4e6f7a8c90",
    "occurred_at": "2026-10-12T09:15:00+03:00",
    "sent_at": "2026-10-12T09:15:02+03:00",
    "data": {
        "session_id": 31,
        "session_number": "RCV-2026-0031",
        "reference": "FACT-ASN-77",
        "completed_at": "2026-10-12T09:15:00+03:00",
        "asn_id": 42,
        "asn_reference": "ASN-2026-0042",
        "lines": [
            { "sku": "A-1", "received_quantity": 5, "batch_number": "B-100", "accepted_quantity": 5, "damaged_quantity": 0 },
            { "sku": "A-2", "received_quantity": 2, "batch_number": null, "accepted_quantity": 0, "damaged_quantity": 2 }
        ]
    }
}

Câmpurile noi sunt adăugate la sfârșit sau în locul indicat, fără a le schimba pe cele existente; un receptor care ignoră câmpurile necunoscute continuă să funcționeze.

Evenimentul stock.low ​

Se trimite când stocul disponibil al unui produs al clientului ajunge la pragul pe care clientul l-a ales pentru el (în portal, la Stoc → Alerte de stoc), sau sub el. Se trimite o singură dată pentru fiecare episod: un nou eveniment apare doar după ce stocul a revenit peste prag și a scăzut din nou. Revenirea peste prag nu trimite nimic. Câmpul data conține, în această ordine: alert_id, sku, warehouse, available, on_hand, threshold și status.

  • alert_id identifică alerta; o retrimitere a aceleiași livrări repetă aceleași cifre.
  • warehouse este codul depozitului pentru care s-a stabilit pragul, sau null când pragul este valabil pentru toate depozitele.
  • available este cantitatea disponibilă pentru comenzi în momentul ridicării alertei; on_hand este stocul fizic din același moment (include marfa blocată, de exemplu în carantină); threshold este pragul în vigoare atunci.
  • status este open.
json
{
    "event": "stock.low",
    "event_id": "5c0e8a13-7b2f-4d94-a6e1-3f8b9c0d1e2a",
    "occurred_at": "2026-10-18T10:00:00+03:00",
    "sent_at": "2026-10-18T10:00:01+03:00",
    "data": {
        "alert_id": 17,
        "sku": "A-1",
        "warehouse": null,
        "available": 5,
        "on_hand": 8,
        "threshold": 5,
        "status": "open"
    }
}

Evenimentul stock.expiring ​

Se trimite când un lot al clientului expiră în fereastra aleasă de client (implicit 30 de zile; 0 oprește alerta). Se trimite o singură dată pentru fiecare lot, depozit și dată de expirare; marfa deja expirată nu declanșează evenimentul. Câmpul data conține, în această ordine: alert_id, sku, warehouse, lot_number, batch_number, expiry_date, days_left, quantity și status.

  • warehouse este codul depozitului în care se află lotul, sau null.
  • lot_number și batch_number pot fi null când stocul nu are lot sau serie.
  • expiry_date este data expirării (YYYY-MM-DD), iar days_left este numărul de zile rămase în momentul ridicării alertei (0 în ziua expirării).
  • quantity este cantitatea totală a lotului în acel depozit, în momentul ridicării alertei.
  • status este open.
json
{
    "event": "stock.expiring",
    "event_id": "8d3f1b64-2a9c-4e75-b0d8-6c7e8f9a0b1c",
    "occurred_at": "2026-10-18T10:00:00+03:00",
    "sent_at": "2026-10-18T10:00:01+03:00",
    "data": {
        "alert_id": 18,
        "sku": "A-2",
        "warehouse": "W1",
        "lot_number": "L-77",
        "batch_number": "B-77",
        "expiry_date": "2026-10-28",
        "days_left": 10,
        "quantity": 7,
        "status": "open"
    }
}

Alertele sunt evaluate din oră în oră, deci un eveniment poate ajunge cu până la o oră după ce stocul a atins pragul sau a intrat în fereastră.

Evenimentul order.cancelled ​

Se trimite când o comandă a clientului este anulată, indiferent cine a anulat-o: clientul, echipa depozitului sau magazinul online. Se trimite o singură dată pentru fiecare comandă; o a doua anulare a aceleiași comenzi nu trimite nimic. Dacă marfa deja culeasă trebuie readusă pe raft, evenimentul se trimite când anularea este încheiată. Câmpul data conține, în această ordine: order_number, external_order_id, cancelled_at, reason și cancelled_by.

  • order_number este numărul comenzii în WMS; external_order_id este identificatorul comenzii în sistemul clientului sau în magazin, sau null.
  • cancelled_at este momentul anulării (același cu occurred_at).
  • reason este motivul înregistrat al anulării, sau null când nu s-a dat unul.
  • cancelled_by spune cine a anulat: client (clientul sau integrarea lui), staff (echipa depozitului sau un flux automat din WMS) sau platform (anularea a venit din magazinul online).
json
{
    "event": "order.cancelled",
    "event_id": "b4e7a2d0-1c58-4f93-8a6b-7d9e0f1a2b3c",
    "occurred_at": "2026-10-18T11:30:00+03:00",
    "sent_at": "2026-10-18T11:30:01+03:00",
    "data": {
        "order_number": "CMD-5001",
        "external_order_id": "EXT-5001",
        "cancelled_at": "2026-10-18T11:30:00+03:00",
        "reason": "Clientul a renunțat",
        "cancelled_by": "client"
    }
}

Evenimentul order.delivered ​

Se trimite când o comandă trece în starea livrat, de exemplu după confirmarea curierului. Se trimite o singură dată pentru fiecare comandă. Câmpul data conține, în această ordine: order_number, external_order_id, delivered_at, awb, carrier și carrier_actual.

  • order_number și external_order_id au același înțeles ca la order.cancelled.
  • delivered_at este momentul în care comanda a fost marcată ca livrată (același cu occurred_at), nu momentul expedierii.
  • awb este numărul de expediere, sau null; carrier este curierul, sau null; carrier_actual este curierul real care transportă coletul, sau null. Când comanda este rezervată printr-un intermediar (de exemplu coleteonline), carrier rămâne codul intermediarului, iar carrier_actual arată curierul real (de exemplu sameday); pentru restul comenzilor cele două sunt egale.
json
{
    "event": "order.delivered",
    "event_id": "c5f8b3e1-2d69-4aa4-9b7c-8e0f1a2b3c4d",
    "occurred_at": "2026-10-18T14:20:00+03:00",
    "sent_at": "2026-10-18T14:20:01+03:00",
    "data": {
        "order_number": "CMD-5002",
        "external_order_id": "EXT-5002",
        "delivered_at": "2026-10-18T14:20:00+03:00",
        "awb": "AWB123",
        "carrier": "cargus",
        "carrier_actual": "cargus"
    }
}

Evenimentul order.held ​

Se trimite când o comandă a clientului ajunge efectiv în așteptare: clientul a cerut așteptarea în portal sau echipa depozitului a pus-o, iar depozitul nu va începe pregătirea ei până nu este scoasă din așteptare. O cerere a clientului pentru o comandă la care depozitul a început deja lucrul nu trimite nimic până când depozitul decide să oprească lucrul; dacă decide să termine lucrul, se trimite order.released cu outcome declined. Câmpul data conține, în această ordine: order_number, external_order_id, held_at, reason și held_by.

  • order_number și external_order_id au același înțeles ca la order.cancelled.
  • held_at este momentul în care așteptarea a devenit activă (același cu occurred_at): momentul cererii, sau momentul deciziei depozitului de a opri lucrul.
  • reason este motivul scris de cine a pus comanda în așteptare.
  • held_by spune cine a cerut așteptarea: client (clientul, din portal) sau staff (echipa depozitului).
json
{
    "event": "order.held",
    "event_id": "d6a9c4f2-3e7a-4bb5-8c8d-9f1a2b3c4d5e",
    "occurred_at": "2026-10-19T10:05:00+03:00",
    "sent_at": "2026-10-19T10:05:01+03:00",
    "data": {
        "order_number": "CMD-5001",
        "external_order_id": "EXT-5001",
        "held_at": "2026-10-19T10:05:00+03:00",
        "reason": "Clientul a cerut oprirea",
        "held_by": "client"
    }
}

Evenimentul order.released ​

Se trimite când o așteptare se încheie: a fost scoasă din așteptare de client sau de depozit, sau depozitul a respins o cerere a clientului pentru o comandă la care lucrul era deja început. Câmpul data conține, în această ordine: order_number, external_order_id, released_at, outcome și released_by.

  • order_number și external_order_id au același înțeles ca la order.cancelled.
  • released_at este momentul în care așteptarea s-a încheiat (același cu occurred_at): momentul scoaterii din așteptare, sau momentul deciziei depozitului pentru declined.
  • outcome este released (așteptarea a fost scoasă) sau declined (depozitul a continuat lucrul și nu a oprit comanda).
  • released_by spune cine a încheiat așteptarea: client sau staff. Pentru declined este întotdeauna staff.
json
{
    "event": "order.released",
    "event_id": "e7bad5a3-4f8b-4cc6-9d9e-0a2b3c4d5e6f",
    "occurred_at": "2026-10-19T14:40:00+03:00",
    "sent_at": "2026-10-19T14:40:01+03:00",
    "data": {
        "order_number": "CMD-5001",
        "external_order_id": "EXT-5001",
        "released_at": "2026-10-19T14:40:00+03:00",
        "outcome": "released",
        "released_by": "client"
    }
}

Antete ​

AntetConținut
Content-Typeapplication/json
X-Wms-EventNumele evenimentului, de exemplu order.shipped.
X-Wms-Event-IdIdentificatorul evenimentului (UUID), identic cu event_id din corp. Același la toate încercările, reîncercările și retrimiterile; folosiți-l pentru deduplicare.
X-Wms-Delivery-IdIdentificator unic al încercării de livrare (UUID v4). Fiecare încercare, reîncercare sau retrimitere are altul.
X-Wms-TimestampMomentul trimiterii, în secunde Unix.
X-Wms-Signature-V2v2= urmat de HMAC-SHA256 în hexazecimal, calculat ca mai jos.
X-Wms-SignatureSemnătura veche: HMAC-SHA256 în hexazecimal peste corpul brut. Se trimite doar în modul both.

Semnătura v2 ​

Semnătura v2 leagă corpul, identificatorul, momentul și evenimentul. Octeții semnați sunt cinci linii ASCII, separate prin câte un singur LF, fără LF după ultima:

text
wms-webhook-v2
<X-Wms-Delivery-Id>
<X-Wms-Timestamp>
<X-Wms-Event>
<sha256 în hexazecimal al corpului brut>

Valoarea antetului este v2= urmat de HMAC-SHA256(secret, octeții de mai sus), în hexazecimal cu litere mici. Prima linie, wms-webhook-v2, separă semnătura v2 de cea veche: o semnătură veche nu poate fi prezentată ca v2.

Semnătura veche din X-Wms-Signature este HMAC-SHA256(secret, corpul brut). Nu protejează împotriva reluării cererilor; folosiți-o doar cât timp receptorul nu a fost actualizat.

Lista de verificare a receptorului ​

  1. Citiți corpul brut, exact cum a sosit. Nu îl serializați din nou.
  2. Dacă lipsește sau este invalid vreun antet v2 (X-Wms-Delivery-Id, X-Wms-Timestamp, X-Wms-Event, X-Wms-Signature-V2), respingeți cererea. Nu reveniți la semnătura veche.
  3. Recalculați semnătura v2 și comparați-o cu antetul într-un mod cu timp constant (de exemplu hash_equals în PHP sau crypto.timingSafeEqual în Node.js).
  4. Acceptați doar cereri al căror X-Wms-Timestamp diferă de ceasul dumneavoastră cu cel mult 300 de secunde (5 minute), în ambele sensuri. Păstrați ceasul serverului sincronizat prin NTP.
  5. Rețineți fiecare X-Wms-Delivery-Id cel puțin 600 de secunde (10 minute, dublul ferestrei de 300 de secunde) și ignorați repetările: răspundeți cu 2xx fără a procesa din nou.
  6. Interpretați JSON-ul abia după toate verificările de mai sus.
  7. Răspundeți cu 2xx cât mai repede. Orice alt cod este înregistrat ca eroare a endpointului.

Fereastra de 300 de secunde limitează cât timp poate fi reutilizată o cerere interceptată; retenția de 600 de secunde garantează că o cerere reluată în fereastră este recunoscută ca duplicat.

Modul de semnare (signature_mode) ​

Fiecare endpoint are un câmp signature_mode:

  • both (implicit): WMS trimite și X-Wms-Signature, și antetele v2. Receptoarele existente continuă să funcționeze.
  • v2_only: WMS trimite doar antetele v2 și nu mai trimite X-Wms-Signature.

Treceți endpointul pe v2_only după ce receptorul verifică semnătura v2. Modul se alege în fereastra de creare sau de editare a webhook-ului, sub Semnături trimise.

Secretul ​

Secretul de semnare se generează la crearea endpointului și se afișează o singură dată; WMS îl păstrează criptat și nu îl mai poate arăta. Dacă l-ați pierdut, generați unul nou.

Rotirea secretului are efect imediat și nu există o perioadă de suprapunere: după rotire, livrările sunt semnate doar cu secretul nou, iar cel vechi nu mai este valid. Actualizați mai întâi receptorul, apoi rotiți secretul.

Reguli pentru destinație ​

Destinația este verificată la salvare, la trimiterea de test și la fiecare livrare:

  • Adresa trebuie să folosească HTTPS, cu un nume de domeniu sau o adresă IP publică. Nu sunt acceptate http, utilizator sau parolă în URL, fragmente (#) și nici nume fără punct, cum este localhost.
  • Numele este rezolvat o singură dată, iar toate răspunsurile sunt verificate. Dacă oricare dintre adrese este privată, locală, de legătură, rezervată sau de documentație, livrarea este refuzată (address_blocked). Conexiunea se face apoi exact la adresa verificată (DNS fixat), deci o schimbare ulterioară a înregistrărilor DNS nu poate redirecționa livrarea către o rețea internă.
  • Redirecționările nu sunt urmate. Un răspuns 3xx este înregistrat ca redirect_blocked. Configurați adresa finală direct.
  • Certificatul serverului este verificat (TLS 1.2 sau mai nou). Un proxy configurat pe serverul WMS nu este folosit; livrarea este refuzată cu proxy_unsupported.
  • Cererea așteaptă cel mult 3 secunde conectarea și 5 secunde în total. Din răspuns se acceptă cel mult 1 MiB, iar corpul nu este păstrat.
  • După 10 eșecuri consecutive ale endpointului, acesta este oprit și trebuie reactivat manual din pagina Webhook-uri. La reactivare, adresa este verificată din nou. Evenimentele apărute cât timp endpointul este oprit nu se pierd: rămân în așteptare și se trimit după reactivare (vezi mai jos).

Reîncercări, stări și deduplicare ​

O livrare eșuată din partea endpointului (răspuns diferit de 2xx, depășirea timpului, eroare de transport, adresă blocată) este reîncercată automat, la intervale crescătoare după încercarea eșuată: 1 min, 5 min, 30 min, 2 h, 6 h. Sunt cel mult șase încercări în total; după a șasea eșuată, livrarea devine dead și nu mai este reîncercată automat.

StareÎnțeles
deliveredEndpointul a răspuns cu 2xx; lanțul s-a încheiat.
retryingÎncercarea a eșuat și urmează alta, la ora afișată în pagina Webhook-uri. Fără oră, evenimentul așteaptă reactivarea endpointului oprit.
deadNu mai urmează nicio încercare automată: au fost folosite toate cele șase, sau WMS nu a putut construi evenimentul (payload_invalid, ownership_mismatch, secret_missing, proxy_unsupported, transport_unavailable).

O refuzare din partea WMS (conținut invalid, date ale altui client) este dead imediat, nu mută contorul de erori al endpointului și nu oprește endpointul. Fiecare încercare eșuată din partea endpointului se adaugă contorului de erori consecutive.

Un membru al echipei WMS poate retrimite din pagina Webhook-uri (sau cu client-webhooks:resend) o livrare dead sau delivered. Retrimiterea este o încercare nouă, cu același event_id și același occurred_at, dar cu X-Wms-Delivery-Id și X-Wms-Timestamp noi. Conținutul se construiește din datele de acum, nu din cele de la prima trimitere, deci poate diferi de cel trimis atunci.

Deduplicați după event_id (antetul X-Wms-Event-Id). Aceeași livrare poate ajunge de mai multe ori: o reîncercare după un răspuns pierdut, o retrimitere. X-Wms-Delivery-Id se schimbă la fiecare încercare și nu este potrivit pentru asta. Rețineți fiecare event_id procesat cât timp aveți nevoie să recunoașteți un duplicat (cel puțin câteva zile, deoarece ultima reîncercare vine la aproximativ 8 ore după prima), și răspundeți cu 2xx fără a procesa din nou un event_id deja văzut.

Coduri de rezultat ​

WMS înregistrează pentru fiecare livrare un cod dintr-o listă închisă, fără textul erorii sau corpul răspunsului. Codurile de mai jos sunt cele afișate în pagina Webhook-uri.

CodÎnțeles
deliveredEndpointul a răspuns cu 2xx.
http_errorEndpointul a răspuns cu un cod HTTP de eroare (stare în coloana separată).
redirect_blockedEndpointul a răspuns cu 3xx; redirecționarea nu este urmată.
url_invalidAdresa salvată nu respectă regulile de mai sus.
dns_unresolvedNumele de domeniu nu poate fi rezolvat.
address_blockedNumele sau adresa duc către o rețea privată sau specială.
connect_failedConexiunea la endpoint a eșuat.
timeoutEndpointul nu a răspuns la timp.
tls_errorCertificatul sau conexiunea TLS sunt invalide.
transport_errorAltă eroare de transport, inclusiv un răspuns mai mare de 1 MiB.
proxy_unsupportedServerul WMS are un proxy configurat, care nu este suportat.
transport_unavailableTransportul securizat nu este disponibil pe serverul WMS.
internal_errorEroare internă la trimitere.
secret_missingEndpointul nu are secret de semnare.
payload_invalidConținutul evenimentului nu a putut fi construit.
ownership_mismatchEvenimentul conținea date ale altui client și nu a fost trimis.

Primele zece coduri țin de endpoint sau de configurarea lui și numără la oprirea automată; ultimele șase țin de WMS și nu o influențează.

Alte webhook-uri trimise de WMS ​

Nodurile webhook din automatizări și regulile de notificare de tip webhook folosesc același transport: adresele private sunt refuzate, DNS este fixat, redirecționările nu sunt urmate și răspunsul este limitat la 1 MiB. Pentru ele, http către o adresă publică rămâne permis, pentru endpointurile configurate înainte de această regulă. Metoda unui nod de automatizare trebuie să fie GET, POST, PUT, PATCH sau DELETE; altfel nodul eșuează cu method_invalid. În jurnale și în rezultatele rulărilor apar doar codul de rezultat și originea destinației (schemă, gazdă și port), niciodată calea, parametrii sau antetele cu credențiale.

Ultima actualizare:

Documentație API notso WMS