Dla firm
API i webhooki dla TMS
Odczytuj dane floty i wysyłaj zlecenia do kierowców z własnego systemu, a o zdarzeniach dowiaduj się natychmiast przez podpisane webhooki. Dostępne w planie Firma.
Uwierzytelnianie
Klucz API tworzy właściciel firmy w panelu (Integracje). Każde zapytanie wysyłaj z nagłówkiem Authorization: Bearer. Klucz widać tylko raz — w bazie trzymamy jego skrót. Klucz ma zakresy uprawnień i działa wyłącznie na danych Twojej firmy.
curl https://truckmaestro.com/api/v1/integrations/drivers \
-H "Authorization: Bearer tt_live_XXXXXXXXXXXXXXXXXXXXXXXX"Limit: 120 zapytań na minutę na klucz (nagłówki X-RateLimit-*, po przekroczeniu 429 i Retry-After).
Endpointy
Wszystkie pod adresem /api/v1/integrations, odpowiedzi w JSON, daty w ISO 8601.
| Metoda | Ścieżka | Zakres |
|---|---|---|
| GET | /api/v1/integrations/ping | — |
| GET | /api/v1/integrations/drivers | read:drivers |
| GET | /api/v1/integrations/drivers/{id} | read:drivers |
| GET | /api/v1/integrations/workdays | read:days |
| GET | /api/v1/integrations/orders | read:orders |
| GET | /api/v1/integrations/orders/{id} | read:orders |
| POST | /api/v1/integrations/orders | write:orders |
| GET | /api/v1/integrations/violations | read:violations |
- Paginacja kursorowa: ?limit=1–100 (domyślnie 50) i ?cursor=…; odpowiedź: items, nextCursor, hasMore.
- Filtry: driverId, from/to (dni pracy, naruszenia), status (zlecenia), updatedSince (dni i zlecenia — lista rośnie po dacie zmiany, więc nextCursor nadaje się do synchronizacji przyrostowej).
- Pozycja kierowcy jest zwracana tylko, gdy kierowca udostępnia lokalizację firmie — tak jak w panelu.
- Błędy mają format error.code and error.message: 401 zły lub odwołany klucz, 402 brak planu Firma, 403 brak zakresu, 429 limit.
Wysyłanie zleceń
POST /orders jest idempotentny po id (UUID nadany przez Twój system): ponowienie zwraca 200 i zapisane zlecenie bez zmian, nowe zlecenie to 201. Kierowca dostaje powiadomienie i widzi zlecenie w aplikacji.
curl -X POST https://truckmaestro.com/api/v1/integrations/orders \
-H "Authorization: Bearer tt_live_XXXXXXXXXXXXXXXXXXXXXXXX" \
-H "Content-Type: application/json" \
-d '{
"id": "0192f3c4-6a5e-7b21-9c3a-1f6d2a7b9e10",
"driverId": "<driver id from /drivers>",
"number": "ZL/11/2026/14", "client": "Raben",
"stops": [
{ "kind": "loading", "place": "Poznań, ul. Głogowska 1", "country": "PL", "plannedAt": "2026-11-10T08:00:00+01:00" },
{ "kind": "unloading", "place": "Berlin, Industriestr. 5", "country": "DE" }
]
}'Webhooki
Wybierz adres HTTPS i zdarzenia w panelu. Każda dostawa to POST z JSON-em i poniższymi nagłówkami.
- workday.finished
- violation.detected
- order.stop.arrived
- order.stop.departed
- permit.expiring
Content-Type: application/json
User-Agent: TruckMaestro-Webhooks/1
X-TruckMaestro-Event: workday.finished
X-TruckMaestro-Event-Id: 0192f3c4-9d4b-7a10-8e55-3b1c0f6a2d77 # the same on every retry — deduplicate on it
X-TruckMaestro-Delivery-Id: …
X-TruckMaestro-Attempt: 1
X-TruckMaestro-Signature: t=1762191730,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd{
"id": "0192f3c4-9d4b-7a10-8e55-3b1c0f6a2d77",
"type": "workday.finished",
"createdAt": "2026-11-03T17:42:10Z",
"companyId": "0192…",
"data": { "driver": { "id": "…", "name": "Marek Kowalski", "email": "…" }, "workDay": { "id": "…", "date": "2026-11-03", "workMinutes": 610, "…": "…" } }
}- Odpowiedz kodem 2xx w ciągu 8 sekund. Przekierowania nie są śledzone.
- Przy błędzie ponawiamy po 1 min, 5 min, 30 min, 2 h i 12 h (6 prób łącznie). Potem dostawę można wysłać ponownie z panelu.
- To samo zdarzenie może przyjść więcej niż raz — deduplikuj po polu id (nagłówek X-TruckMaestro-Event-Id).
- Po 10 nieudanych próbach z rzędu wyłączamy webhook i informujemy właściciela.
- Adresy muszą być publiczne i zaczynać się od https://; adresy lokalne i prywatne są blokowane.
Weryfikacja podpisu
Podpis to HMAC-SHA256 z tekstu „t.surowe_ciało” (znacznik czasu, kropka, surowe ciało) kluczem będącym sekretem webhooka (whsec_…). Licz go z surowych bajtów żądania, porównuj w stałym czasie i odrzucaj zbyt stare znaczniki t (np. starsze niż 5 minut).
Node.js
import crypto from 'node:crypto'
// rawBody: the request body exactly as received (Buffer/string) — NOT re-serialised JSON
export function verify(rawBody, signatureHeader, secret, toleranceSec = 300) {
const parts = Object.fromEntries(signatureHeader.split(',').map(p => p.trim().split('=')))
const t = Number(parts.t)
if (!t || Math.abs(Date.now() / 1000 - t) > toleranceSec) return false // replay protection
const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex')
const a = Buffer.from(expected), b = Buffer.from(parts.v1 ?? '')
return a.length === b.length && crypto.timingSafeEqual(a, b)
}PHP
function verify(string $rawBody, string $header, string $secret, int $tolerance = 300): bool
{
parse_str(str_replace(',', '&', $header), $p); // t=…&v1=…
if (!isset($p['t'], $p['v1']) || abs(time() - (int) $p['t']) > $tolerance) {
return false;
}
return hash_equals(hash_hmac('sha256', $p['t'].'.'.$rawBody, $secret), $p['v1']);
}Python
import hmac, hashlib, time
def verify(raw_body: bytes, header: str, secret: str, tolerance: int = 300) -> bool:
parts = dict(p.strip().split("=", 1) for p in header.split(","))
t = int(parts.get("t", 0))
if not t or 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", ""))