Partner API: sending orders and receiving callbacks

Order submission (inbound HMAC in hex), order.created and order.status_changed callbacks (outbound HMAC sha256=), reconciliation endpoints, dedupe by X-N1-Delivery-Id, retries and circuit breaker auto-recovery.

In this guide you will…

  1. Build the base URL and the POST /api/webhooks/partner/orders endpoint
  2. Sign inbound requests (partner → N1) in hex, with no prefix
  3. Validate outbound callbacks (N1 → partner) using sha256= in the header
  4. Interpret response codes and the PARTNER-{identifier}-{external_order_id} ID

Base URLs

Every route uses the same host as your N1 instance (where the panel is hosted). For example:

  • https://YOUR-N1-DOMAIN.com/api/webhooks/partner/orders
  • https://YOUR-N1-DOMAIN.com/api/webhooks/partner/docs
  • https://YOUR-N1-DOMAIN.com/api/webhooks/partner/health

Replace YOUR-N1-DOMAIN.com with the real domain.

Inbound — sending orders (POST /api/webhooks/partner/orders)

Required headers

  • Content-Type: application/json
  • X-API-Key: key in the pk_live_ or pk_test_ format (the one shown when you created the API Key)
  • X-Webhook-Timestamp: Unix time in seconds (integer)
  • X-Webhook-Signature: HMAC-SHA256 signature in hex ( without the sha256= prefix )

How to compute the signature (inbound)

  1. Signed string: timestamp + "." + raw JSON body — the dot is literal, and the body must be exactly the bytes sent in the request (same key order, spaces and line breaks as sent)
  2. HMAC-SHA256(signed_string, webhook_secret) → output in hexadecimal
  3. Send that hex value in X-Webhook-Signature
signature = HMAC_SHA256_hex( timestamp + "." + rawBody, webhook_secret )

Time window

X-Webhook-Timestamp must be within roughly 5 minutes of the server clock (anti-replay). Timestamps outside the window produce a signature/timestamp error.

Responses and errors

  • 201: order created successfully (the body includes order_id)
  • 400: invalid payload (Zod schema) — fix the fields listed in details
  • 401: invalid/inactive API Key or invalid HMAC signature or missing/invalid timestamp — check the secret, the signed string and X-Webhook-Timestamp
  • 403: IP not allowed — when an IP whitelist is configured for that key (adjust via administrator/API/support; there is no screen for this in the API Keys panel)
  • 409: duplicate order — same external_order_id for the same partner identifier
  • 429: rate limit — wait and retry (the response carries a retry_after header in seconds)

The order ID in N1

The internal ID generated is:

PARTNER-{partnerIdentifier}-{external_order_id}

Use the same external_order_id you sent in the JSON to correlate with the source system.

Outbound — callbacks (N1 → your endpoint)

When an order is created or changes status, N1 can POST to your callbackUrl (if configured). The full documentation with validation examples lives at GET /api/webhooks/partner/docs and in the integration prompt (button in the API Keys panel).

Events

  • order.created: fired immediately after a successful creation (useful as an auditable record beyond the HTTP 201 response). Requires "created" in the API key's callback_events.
  • order.status_changed: fired on any order status change, from any source (internal ops, carrier, 17TRACK, syncs).

The X-N1-Event header tells you which event was delivered.

Headers + signature

  • X-N1-Signature: sha256= + hex (different from inbound, which is hex only)
  • X-N1-Timestamp: Unix time in seconds. Your validation must reject anything outside the 5-minute window.
  • X-N1-Event: order.created or order.status_changed
  • X-N1-Delivery-Id: unique UUID — your code MUST deduplicate on this value (TTL ≥ 30 days). The same delivery can arrive more than once (retries, manual replay).
  • Signed payload: timestamp + "." + rawBody.

Retries, dead-letter and circuit breaker

  • Your endpoint must answer 2xx. Any other status (or a network error) triggers a retry: 3 attempts with 1min → 5min backoff → dead-letter.
  • Circuit breaker (auto-recovery): after 10 consecutive failures, N1 stops sending for 30 minutes. If it still fails when reopening, the wait doubles: 60 min → 120 min → … capped at 24 h. Any 2xx response resets it. No manual action is required (the reactivation button in the panel is only an override).

Reconciliation (partner auto-recovery)

If your endpoint went down or you missed callbacks, three GET/POST endpoints let you recover without asking for manual help. They require the same 6 security layers as the creation POST; for GET, the signed string is timestamp + "." + URL-path-with-query-string.

  • GET /api/webhooks/partner/orders/:external_order_id — current state of one order.
  • GET /api/webhooks/partner/orders?updated_since=ISO&limit=100&offset=0 — list orders changed after the timestamp (paginated).
  • POST /api/webhooks/partner/orders/:external_order_id/replay-callbacks — re-queues the last N callbacks for that order (body: { "limit": 5 }, default 5, max 20).

Full examples at /api/webhooks/partner/docs and in the panel's LLM prompt.

Service health

GET /api/webhooks/partner/health — public response { "status": "ok", ... } for monitoring. No authentication required.

Limits and the panel

Rate limiting and IP whitelisting exist in the API; if they do not appear in your panel, treat them as adjustable via administrator or support. Do not assume buttons that are not in the interface.

Related article

Panel setup (creating a key, secret, callback): Partner API Keys — panel.