Skip to content

Webhooks

A webhook is a POST from Orda to your URL when something happens — no polling, statuses arrive in seconds.

Окно терминала
curl -s -X POST "$ORDA_API/webhooks" -H "X-Api-Key: $ORDA_KEY" -H "Content-Type: application/json" \
-d '{ "url": "https://erp.example.com/orda/webhook",
"events": ["order.status_changed", "order.delivered", "order.failed", "plan.approved"] }'

The secret (whsec_…) in the response is the signing secret, shown once. New secret: POST /webhooks/{id}/rotate-secret (the old one stops at once). Test event: POST /webhooks/{id}/test. Key scopes: integrations.view/create/edit/delete. The URL must be https, public (not localhost or a private network) and contain no user name or password.

Event When
order.status_changed an order status changed (except draft new ↔ planned)
order.delivered delivered
order.failed not delivered (reason when known)
route.planned a route was built in a draft plan (may still change)
plan.approved the day plan was approved: routes are final
route.started the driver started the shift in Orda Driver: the route’s orders are in transit
stop.arrived the driver pressed “I’m here” (with the distance from the point)
pod.created proof of delivery: delivered (signature, photos, payment) or not delivered (reason)
ping test event
POST /orda/webhook HTTP/1.1
Content-Type: application/json
User-Agent: OrdaTMS-Webhooks/1.0
Orda-Event: order.delivered
Orda-Event-Id: 7f9c2b1e-…
Orda-Delivery-Id: 0c41d7a2-…
Orda-Attempt: 1
Orda-Signature: t=1763370000,v1=5f2b…c9
{ "id": "7f9c2b1e-…", "type": "order.delivered", "createdAt": "2026-11-20T09:47:12.531Z", "apiVersion": "v1",
"data": { "orderId": "4b0c…", "externalId": "SO-00001", "number": "SO-00001",
"previousStatus": "in_transit", "status": "delivered" } }

route.planned: planId, routeId, date, vehicleId, driverId, timezone, leaveAt, returnAt, distanceKm, stops[] (seq, orderId, externalId, number, arriveAt). plan.approved: planId, date, orders[] (orderId, externalId, number). route.started: routeId, planId, date, driverId, vehicleId, startedAt, startMode (depot/home), orders[] (orderId, externalId, number). stop.arrived: routeId, stopId, orderId, externalId, number, driverId, arrivedAt, location, distanceM (from the point, when known), outsideRadius (farther than the company radius, 300 m by default). pod.created: orderId, externalId, number, routeId, stopId, driverId, outcome (delivered/failed), reason, action (return_to_warehouse/retry_tomorrow), recipientName, hasSignature, photoIds[], payment (method, amount, currency), location, happenedAt. Photos and signature: GET /api/v1/orders/{id}/proof (photos may arrive later: the driver uploads them after the action, when there is a network).

Orda-Signature: t=<unix time>,v1=<signature>; the signature is hex HMAC-SHA256 of "<t>.<raw body>" with your secret. Verify the raw body (before JSON parsing) and reject timestamps older than 5 minutes.

// Node.js (Express): app.post('/orda/webhook', express.raw({ type: 'application/json' }), handler)
import { createHmac, timingSafeEqual } from 'node:crypto';
export function verifyOrdaSignature(rawBody, header, secret, toleranceSec = 300) {
const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')));
const t = Number(parts.t);
if (!t || Math.abs(Date.now() / 1000 - t) > toleranceSec) return false;
const expected = createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
const given = Buffer.from(parts.v1 ?? '', 'hex');
return given.length === 32 && timingSafeEqual(given, Buffer.from(expected, 'hex'));
}
# Python (Flask / FastAPI): use request.get_data() / await request.body()
import hashlib, hmac, time
def verify_orda_signature(raw_body: bytes, header: str, secret: str, tolerance: int = 300) -> bool:
parts = dict(p.split("=", 1) for p in header.split(","))
t = int(parts.get("t", "0"))
if abs(time.time() - t) > tolerance:
return False
expected = hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, parts.get("v1", ""))
<?php // PHP
function verify_orda_signature(string $rawBody, string $header, string $secret, int $tolerance = 300): bool {
parse_str(str_replace(',', '&', $header), $p);
$t = (int)($p['t'] ?? 0);
if (abs(time() - $t) > $tolerance) return false;
$expected = hash_hmac('sha256', $t . '.' . $rawBody, $secret);
return hash_equals($expected, $p['v1'] ?? '');
}

For 1C see 1C.

  • Answer 2xx within 10 seconds; do heavy work afterwards (in a queue).
  • Anything else, a timeout or a connection error → retry after 1 min, 5 min, 30 min, 2 h, 6 h, 12 h, 24 h (8 attempts, ~45 hours), then the delivery is failed; re-send it with POST /webhooks/deliveries/{id}/retry.
  • Redirects are not followed.
  • Events of one endpoint are sent in order, but retries can reorder them — compare createdAt.
  • The same event may arrive twice; de-duplicate by the event id (Orda-Event-Id).

GET /webhooks/{id}/deliveries?status=failed — recent deliveries: event, attempts, your server’s status code, error text, next attempt time and the event body.