Nexus Gateway
EnglishRomână
OpenAPI

Înregistrează un singur URL de callback pentru tenant. Gateway-ul trimite actualizări semnate ale comenzilor, disponibilității restaurantului și produselor, publicării meniului și conexiunilor POS prin acest singur callback. Conexiunile activate ulterior din Retail sunt descoperite automat.

URL-ul gateway-ului este destinația cererilor API trimise de backendul tău. URL-ul de callback aparține backendului tău și este destinația notificărilor trimise de Nexus. Înlocuiește https://shop.example.com/nexus/events de mai jos cu propriul receptor HTTPS. Fiecare tenant are propria configurație de callback, folosită de toate conexiunile sale POS. Dacă mai multe website-uri folosesc aceeași cheie de tenant, ele folosesc același abonament: o nouă cerere PUT /v1/webhook îl actualizează, nu adaugă un al doilea receptor. Dacă este necesar, distribuie evenimentele către acele website-uri din backendul tău.

Înregistrează URL-ul de callback

Folosește cheia website a tenantului din backend. Serverul receptor trebuie să utilizeze HTTPS public pe portul 443, cu certificat valid. Adresele private, redirecționările, credențialele din URL, parametrii de interogare și fragmentele nu sunt acceptate.

curl --fail-with-body -X PUT "https://website.api.pionier.ro/v1/webhook" \
  -H "Authorization: Bearer $NEXUS_WEBSITE_KEY" \
  -H "Content-Type: application/json" \
  --data '{"url":"https://shop.example.com/nexus/events","enabled":true}'

Răspunsul conține url, enabled și un signing_secret care începe cu whsec_. Salvează secretul în siguranță pe server. Este întors doar la creare, schimbarea URL-ului sau înlocuirea explicită; cererile PUT identice și GET întorc null. Dacă pierzi primul răspuns, înlocuiește secretul.

503 webhooks_not_configured înseamnă că Nexus trebuie să activeze semnarea webhook-urilor pe acest gateway. Endpoint-urile existente pentru meniu, comenzi și citirea stării rămân disponibile.

Înțelege evenimentul

webhook-event.json
{
  "id": "evt_0123456789abcdef0123456789abcdef0123456789abcdef",
  "type": "order.status_changed",
  "sequence": 1,
  "observed_at": "2026-10-19T10:00:00Z",
  "connection_id": "website-example-pos",
  "data": {
    "external_order_id": "shop-1001",
    "status": "accepted",
    "total": 3700,
    "currency": "RON"
  }
}

order.status_changed raportează o stare confirmată observată de gateway. Acesta verifică comenzile urmărite aproximativ la fiecare 10 secunde după o citire reușită, cu pauze mai mari la erori. Încărcarea cozii și întreruperile pot întârzia livrarea; intervalul nu este garantat. Stările intermediare de scurtă durată pot fi omise. observed_at reprezintă momentul observării, nu momentul acțiunii din restaurant.

Sunt urmărite doar comenzile noi confirmate cât timp webhook-ul este activat. Nu se trimite istoricul și nici un eveniment pentru confirmarea inițială a primirii. Urmărirea se oprește la completed, rejected sau cancelled. Nu există evenimente pentru încasarea plăților. Rapoartele explicite de livrare și rambursare emit și evenimentele descrise mai jos, inclusiv după ce comanda ajunge într-o stare finală. Datele trimise nu conțin informații de contact ale clientului.

Folosește împreună connection_id și data.external_order_id pentru identificarea comenzii. sequence crește pentru fiecare comandă: nu suprascrie o stare mai nouă cu o secvență mai veche. Folosește endpoint-ul de stare după întreruperi sau când ai nevoie de cea mai recentă stare confirmată.

order.updated transmite o estimare actualizată sau alte metadate fără schimbarea stării. Ambele tipuri de eveniment folosesc aceeași secvență per comandă și conțin scheduled_for, estimated_ready_at și reason când sunt disponibile. Procesează ambele tipuri și reconciliază prin GET pentru stare.

Disponibilitatea restaurantului

connection.availability_changed raportează disponibilitatea comenzilor pentru un POS. Procesează-l separat de evenimentele comenzilor: nu conține external_order_id. Câmpul data conține accepting_orders, reason (accepting, paused, closed sau menu_not_published) și marcajul temporal opțional pause_until. Pauza, reluarea manuală, expirarea automată a pauzei și orele de deschidere/închidere pot schimba această stare. Expirarea pauzei permite comenzile doar dacă programul de funcționare le permite.

Folosește același callback și același secret de semnare. Se trimite o stare inițială la începerea urmăririi și din nou după reactivarea webhook-ului sau înlocuirea secretului. Conexiunile sunt urmărite automat prin descoperire sau când gateway-ul le vede prima dată prin publicare ori o cerere autentificată; nu este necesară o comandă de la client. Verificarea rulează aproximativ la fiecare 10 secunde per conexiune urmărită, cu pauze mai mari la erori de registru. Încărcarea și întreruperile pot întârzia notificarea, iar schimbările intermediare rapide pot fi omise. O eroare de registru nu generează o schimbare fictivă a disponibilității.

Păstrează separat cea mai mare valoare sequence per connection_id pentru disponibilitate; nu o compara cu secvența unei comenzi. Aplică accepting_orders la finalizarea comenzilor imediate. Pentru o oră programată, citește meniul cu fulfillment_at pentru evaluarea acelui interval. Reconciliază prin GET meniu după întreruperi; trimiterea comenzii reverifică întotdeauna disponibilitatea curentă. Evenimentele folosesc aceleași reguli de semnare, reîncercare, deduplicare și reluare ca evenimentele comenzilor. Istoricul livrărilor întoarce external_order_id: null pentru ele. Evenimentele de disponibilitate sunt ordonate separat per conexiune și nu blochează evenimentele comenzilor.

Meniuri, produse și opțiuni

menu.published indică publicarea unei revizii noi a meniului: actualizează GET /v1/connections/{connection_id}/menu pentru prețuri, produse, opțiuni, meniuri combo și traduceri. Reîncercările identice ale publicării nu repetă evenimentul.

menu.availability_changed indică modificarea datelor care determină disponibilitatea: stocul, restricțiile produselor/opțiunilor sau programul produselor, inclusiv deschiderea ori închiderea unui interval. Se trimite și o notificare inițială. Recitește același endpoint de meniu; folosește fulfillment_at pentru un interval viitor. Este o notificare de reîmprospătare, nu lista articolelor modificate: un articol poate rămâne indisponibil din alt motiv. Publicarea poate genera ambele evenimente de meniu; grupează reîmprospătările.

Ambele tipuri conțin data: {"revision":"menu-2026-12-25"}. Disponibilitatea se poate schimba fără o revizie nouă. Păstrează secvențe separate pentru fiecare (connection_id, type). La citire, meniul poate fi deja mai nou decât notificarea.

Setările de funcționare

connection.settings_changed cere recitirea GET /v1/connections/{connection_id}/menu când se schimbă programul săptămânal, excepțiile de sărbători, fusul orar al restaurantului sau timpul de preparare. Se emite chiar dacă accepting_orders rămâne neschimbat. Citește opening_hours și scheduling din meniul actualizat pentru intervalele viitoare de comandă și estimările de preparare.

Datele sunt data: {"settings_version":"VALOARE_HEX_OPACA_DE_64_CARACTERE"}. Valoarea identifică setările observate; folosește secvența evenimentului, nu hash-ul, pentru ordonare. Evenimentul cere reîmprospătarea, nu conține o copie a setărilor: citește valorile curente și nu folosi hash-ul ca revizie de meniu. Are o secvență independentă per conexiune/tip, folosește callback-ul semnat și reîncercările existente și întoarce external_order_id: null în istoricul livrărilor.

Urmărirea începe după publicarea unui meniu pentru conexiune. Notificarea inițială se repetă după reactivarea callback-ului, schimbarea URL-ului sau înlocuirea secretului de semnare. Observatorul existent de disponibilitate detectează schimbările aproximativ la 10 secunde, cu întârzieri posibile la încărcare sau întreruperi; modificările rapide pot fi grupate. Schimbările politicii de funcționare aplicate în timp real nu necesită republicarea meniului. Reconciliază prin GET meniu după notificări pierdute.

Conexiunile POS

connection.changed raportează starea inițială și apoi activarea/dezactivarea sau modificarea numelui locației/POS-ului. Câmpul data conține enabled, location_id, location_name, pos_id și pos_name. Recitește GET /v1/connections pentru actualizarea locațiilor. Dacă este dezactivată, elimină conexiunea din finalizarea comenzii; cererile de meniu și comenzi pentru ea nu mai sunt autorizate.

Conexiunile POS noi sunt descoperite automat, fără o cerere de la website sau publicarea unui meniu. Descoperirea citește pagini de cel mult 100 de conexiuni și reîncepe aproximativ la 10 secunde după încheierea unei parcurgeri. Disponibilitatea este verificată tot aproximativ la 10 secunde. Încărcarea, reîncercările și întreruperile pot întârzia notificările; stările intermediare rapide pot fi omise. O eroare de registru nu inventează o stare dezactivată.

Notificarea finală de dezactivare a POS-ului ajunge în continuare la callback-ul tenantului. Dezactivarea tenantului sau a webhook-ului oprește toate încercările ulterioare de livrare. Evenimentele de conexiune și meniu folosesc același contract de semnare, reîncercare și reluare ca comenzile, cu external_order_id: null în istoricul livrărilor. Fiecare (connection_id, type) are o secvență ordonată independentă; stările inițiale sunt retrimise după reactivarea webhook-ului, schimbarea URL-ului sau înlocuirea secretului. După întreruperi, reconciliază lista conexiunilor și fiecare meniu activ.

Verifică semnătura

Citește X-Nexus-Signature: t=TIMESTAMP,v1=SIGNATURE. Calculează HMAC-SHA256 peste timestamp, un punct literal și octeții exacți ai corpului brut al cererii. Folosește întregul șir whsec_... drept cheie UTF-8. Compară în timp constant și respinge timestamp-urile aflate la mai mult de cinci minute de ceasul serverului. Sincronizează ceasurile. Fiecare încercare de livrare primește o semnătură nouă.

verify_webhook.py
"""Verify before parsing JSON. Supply the unmodified HTTP request body and header."""
import hashlib
import hmac
import time


def verify_webhook(body: bytes, header: str, signing_secret: str, now=None):
    if len(header) > 256 or len(body) > 16384:
        raise ValueError("Invalid webhook")
    try:
        parts = header.split(",")
        if len(parts) != 2 or not parts[0].startswith("t=") or not parts[1].startswith("v1="):
            raise ValueError()
        stamp, actual = parts[0][2:], parts[1][3:]
        if not stamp.isascii() or not stamp.isdigit() or len(stamp) > 12:
            raise ValueError()
        current = time.time() if now is None else now
        if abs(current - int(stamp)) > 300:
            raise ValueError()
        expected = hmac.new(signing_secret.encode("utf-8"), stamp.encode("ascii") + b"." + body, hashlib.sha256).hexdigest()
        if not hmac.compare_digest(expected, actual):
            raise ValueError()
    except (ValueError, TypeError):
        raise ValueError("Invalid webhook") from None
    return True

Verifică înainte de interpretarea JSON-ului. După verificare, validează tipul evenimentului și câmpurile obligatorii. X-Nexus-Event-Id repetă id din corpul semnat; folosește valoarea semnată. Callback-ul nu include niciodată cheia API website.

Confirmă și elimină duplicatele

  1. Verifică semnătura și salvează evenimentul în propria coadă, cu o constrângere de unicitate pe id.
  2. Întoarce rapid orice răspuns 2xx, inclusiv pentru un eveniment deja salvat. Procesează-l asincron.
  3. Aplică actualizările folosind cea mai mare secvență procesată pentru comandă. Păstrează înregistrările de deduplicare cât timp permiți retrimiteri manuale.

Livrarea este de tip „cel puțin o dată”, deci pot apărea duplicate chiar și după un răspuns reușit. Timeout-urile, erorile de conexiune și toate răspunsurile non-2xx produc reîncercări cu intervale crescătoare exponențial și variație aleatoare. Valorile numerice Retry-After, în secunde, sunt respectate până la o oră. Încercările automate se opresc după 30 de încercări sau după o eroare produsă la cel puțin 24 de ore de la introducerea în coadă, oricare apare prima. O întrerupere a gateway-ului poate prelungi timpul real până la ultima încercare.

Actualizările ulterioare ale aceleiași comenzi așteaptă cât timp un eveniment anterior este în așteptare. După succesul acestuia sau epuizarea reîncercărilor, evenimentele ulterioare pot continua. O retrimitere manuală poate astfel să ajungă după o stare mai nouă.

Verifică și reîncearcă livrările eșuate

curl --fail-with-body "https://website.api.pionier.ro/v1/webhook/deliveries" \
  -H "Authorization: Bearer $NEXUS_WEBSITE_KEY"

curl --fail-with-body -X POST "https://website.api.pionier.ro/v1/webhook/deliveries/$NEXUS_EVENT_ID/retry" \
  -H "Authorization: Bearer $NEXUS_WEBSITE_KEY"

Lista întoarce ultimele 100 de evenimente, cu stările pending, delivered, failed sau cancelled, numărul de încercări, ultimul status HTTP și coduri de eroare fără date sensibile. Corpurile răspunsurilor receptorului nu sunt expuse. Evenimentele rămân în registrul gateway-ului; limita de 100 de intrări se aplică afișării, nu păstrării datelor.

Reîncercarea se aplică doar evenimentelor eșuate pentru callback-ul curent activat. Întoarce 204 și păstrează ID-ul, corpul și secvența originale, resetând fereastra de încercări. Celelalte ID-uri întorc 404 failed_delivery_not_found. Rezolvă mai întâi problema receptorului și verifică starea curentă a comenzii.

Dezactivează sau înlocuiește secretul

Trimite PUT cu același URL și enabled: false pentru a suspenda urmărirea și a anula livrările în așteptare. Reactivarea reia urmărirea comenzilor nefinalizate urmărite anterior; comenzile confirmate cât timp webhook-ul era dezactivat nu sunt preluate retroactiv. Schimbarea URL-ului sau POST la /v1/webhook/rotate-secret generează un secret nou și anulează evenimentele din coadă pentru configurația veche. O cerere HTTP deja trimisă poate totuși să ajungă.

Dezactivarea unui POS sau tenant din Retail/Administration blochează încercările ulterioare de callback pentru acesta. Înlocuirea cheii API website nu înlocuiește secretul webhook-ului. Consultă referința API pentru toate câmpurile cererilor și răspunsurilor.

Rapoarte de livrare și rambursare

order.delivery_updated conține external_order_id, request_id, status al livrării trimis și estimări opționale. refund.reported conține ID-ul comenzii, ID-ul cererii, outcome și amount. Acestea au payloaduri proprii, nu OrderReceipt. Contactele curierului și referințele procesatorului sunt excluse. Procesează după type; citește starea curentă pentru reconciliere. Un raport poate sosi după un progres ulterior.

Notificările explicite funcționează și pentru comenzile finalizate, fără repornirea interogării stării. Sunt puse în coadă înaintea unui răspuns reușit la raport și deduplicate după ID-ul cererii în configurația webhook curentă. După timeout sau 503, reîncearcă raportul neschimbat: reîncercarea repară și o întrerupere a punerii notificării în coadă. Webhook-ul trebuie să fie activ la confirmarea raportului; rapoartele trimise cât timp este dezactivat nu sunt recuperate retroactiv. Observarea stării poate emite separat order.updated.

Nexus GatewayAPI pentru comenzi website · v1.0.0

Tastează pentru a căuta în ghidurile de integrare.