Перейти к содержимому

Вебхуки

Вебхук — это POST-запрос от Orda на ваш адрес, когда что-то произошло. Не нужно опрашивать API: статус доставки приходит через секунды.

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

В ответе поле secret (whsec_…) — секрет подписи, показывается один раз. Новый секрет: POST /webhooks/{id}/rotate-secret (старый перестаёт действовать сразу). Тестовое событие ping: POST /webhooks/{id}/test. Права ключа: integrations.view/create/edit/delete.

Требования к адресу: https, публичный адрес (не localhost и не внутренняя сеть), без логина и пароля в URL.

Событие Когда
order.status_changed статус заказа изменился (кроме черновых new ↔ planned)
order.delivered заказ доставлен
order.failed заказ не доставлен (reason, если указана)
route.planned построен маршрут в черновике плана (может ещё измениться)
plan.approved план дня утверждён: маршруты окончательные
route.started водитель начал смену в Orda Driver: заказы маршрута «в пути»
stop.arrived водитель нажал «Я на месте» (с расстоянием до точки)
pod.created подтверждение доставки: доставлено (подпись, фото, оплата) или не доставлено (причина)
ping тестовое событие
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": "РТ-00001", "number": "РТ-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 (от точки, если известно), outsideRadius (дальше радиуса из настроек компании, по умолчанию 300 м). 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. Фото и подпись — GET /api/v1/orders/{id}/proof (фото могут догрузиться позже: водитель отправляет их после действия, когда есть сеть).

Orda-Signature: t=<время Unix>,v1=<подпись>, где подпись — HMAC-SHA256 от строки "<t>.<тело запроса>" с вашим секретом, в hex. Проверяйте сырое тело запроса (до разбора JSON) и отклоняйте метки старше 5 минут.

// 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): используйте 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'] ?? '');
}

Для 1С — см. 1С (HMAC считается встроенными средствами платформы).

  • Ответьте 2xx за 10 секунд. Тяжёлую обработку делайте после ответа (в очереди).
  • Любой другой ответ, таймаут или ошибка соединения → повтор через 1 мин, 5 мин, 30 мин, 2 ч, 6 ч, 12 ч, 24 ч (всего 8 попыток, ~45 часов). Затем доставка помечается failed; её можно отправить снова: POST /webhooks/deliveries/{id}/retry.
  • Редиректы (3xx) не выполняются — укажите окончательный адрес.
  • События одного адреса отправляются по порядку возникновения, но при повторах порядок может нарушиться — сверяйте createdAt и храните последний статус заказа.
  • Одно и то же событие может прийти дважды (например, вы обработали, но ответ не дошёл). Используйте id события (Orda-Event-Id) для защиты от повторной обработки.

GET /webhooks/{id}/deliveries?status=failed — последние доставки: событие, попытки, код ответа вашего сервера, текст ошибки, время следующей попытки и само тело события.