# Śledzenie przesyłek

Przesyłkę można śledzić na dwa sposoby: **odpytywać** endpoint `tracking`
albo pozwolić platformie **wypychać** zmiany statusów na Twój endpoint. Push
to lepszy domyślny wybór — bez pętli odpytywania, aktualizacje przychodzą na
bieżąco.

Oba kanały używają **różnych słowników statusów** — zobacz
[Statusy odpytywania](#statusy-odpytywania) i
[Statusy webhooków](#statusy-webhooków). Nie używaj dla nich jednego
wspólnego mapowania.

## Odpytywanie — endpoint `tracking`

Pytaj po **numerze listu przewozowego przewoźnika** (zwróconym przez
`order_send`):

```php
$tracking = $client->getTracking($waybillNumber);

foreach ($tracking->getEvents() as $event) {
    $event->getStatus();          // status znormalizowany, np. IN_TRANSIT
    $event->getStatusOriginal();  // surowy status przewoźnika
    $event->getDescription();
    $event->getPlace();
    $event->getUpdatedAt();       // ISO 8601 w UTC, np. 2026-07-21T08:15:00+00:00
}
```

Każde zdarzenie zawiera zarówno **status znormalizowany** (spójny między
przewoźnikami), jak i oryginalny status przewoźnika. Logikę buduj na
znormalizowanym; oryginalny pokazuj zespołowi wsparcia. Zdarzenia są
zwracane od najnowszego.

Bez SDK: `POST tracking/<waybill_number>/` — zobacz
[referencję API v2](/pl/reference/v2-legacy).

### Statusy odpytywania

| Status | Znaczenie |
|---|---|
| `NEW` | Zamówienie utworzone, paczka nie jest jeszcze u przewoźnika |
| `PICKUP` | Nadana / odebrana przez kuriera |
| `IN_TRANSIT` | W drodze — także wydana do doręczenia i czekająca w punkcie odbioru |
| `DELIVERED` | Doręczona |
| `DELIVERED_NO_SYSTEM` | Doręczona, potwierdzona poza systemem przewoźnika |
| `EXCEPTION` | Wymaga uwagi — nieudana próba doręczenia, awizo, nierozpoznany status przewoźnika |
| `RETURNED_TO_SHIPPER` | Zwrot do nadawcy (także paczki nieodebrane w terminie) |
| `CANCELLED` | Anulowana |
| `CORRECTION` | Reklamacja / korekta — w panelu widoczna jako anulowana |
| `UNKNOWN` | Nie udało się ustalić statusu |

## Webhooki push tracking

Wskaż platformie, dokąd wysyłać zmiany statusów — na jeden z dwóch
sposobów:

- **Per zamówienie** — wyślij `push_tracking_url` razem z zamówieniem. Z
  [SDK](/pl/sdk): `OrderRequest::create()->...->setPushTrackingUrl('https://example.com/apaczka/tracking')`.
- **Per aplikacja WebAPI** — ustaw domyślny URL webhooka trackingu w
  ustawieniach WebAPI w panelu klienta. Dotyczy wszystkich zamówień
  utworzonych kluczami tej aplikacji; `push_tracking_url` podany w
  zamówieniu ma pierwszeństwo.

Platforma będzie wtedy POST-ować **podpisaną notyfikację JSON** na ten URL
przy każdej zmianie statusu. Notyfikacja jest podpisana kluczami `app_id` /
`app_secret` aplikacji WebAPI, która utworzyła zamówienie — zawsze
weryfikuj podpis przed przetworzeniem
([dlaczego](/pl/guides/best-practices#weryfikuj-podpisy-webhooków)). Z
[SDK](/pl/sdk):

```php
use AlsendoOne\SDK\Exception\WebhookVerificationException;
use AlsendoOne\SDK\Webhook\PushTrackingWebhook;

$webhook = new PushTrackingWebhook('your_app_id', 'your_app_secret');

try {
    $notification = $webhook->parse(file_get_contents('php://input'));
} catch (WebhookVerificationException $e) {
    http_response_code(400);   // niezgodny podpis — nie przetwarzaj
    exit;
}

foreach ($notification->getStatuses() as $status) {
    updateShipmentStatus(
        $notification->getOrderNumber(),   // id zamówienia zwrócone przez order_send
        $status->getStatus(),              // np. DELIVERED
        $status->getUpdatedAt()
    );
}

http_response_code(200);   // potwierdź odbiór notyfikacji
```

### Payload

```json
{
  "signature": "3f1c…e9a0",
  "response": {
    "orderNumber": "123456789",
    "statuses": [
      {
        "status": "ON_THE_WAY",
        "updatedAt": "2026-10-06T00:45:00",
        "trackingOperatorStatuses": [
          {
            "status": "IT",
            "description": "In transit",
            "place": "Warszawa",
            "updatedAt": "2026-10-06T00:45:00"
          }
        ]
      }
    ]
  }
}
```

- `orderNumber` — `id` zamówienia w Apaczce (zwrócone przez `order_send`),
  nie numer listu przewozowego. Po nim dopasujesz notyfikację do swojego
  zamówienia.
- `statuses[].status` — status znormalizowany, zobacz
  [Statusy webhooków](#statusy-webhooków).
- `statuses[].trackingOperatorStatuses` — oryginalne zdarzenia przewoźnika
  stojące za tym statusem (`status`, `description`, `place`, `updatedAt`).

### Weryfikacja podpisu bez SDK

Podpis to HMAC-SHA256 z `{app_id}::{json}:` — ten sam schemat co przy
[podpisywaniu żądań](/pl/legacy/hmac-signing), z pustą trasą i czasem
wygaśnięcia — gdzie `{json}` to obiekt `response` ponownie zakodowany bez
escapowania unicode, ukośników i znaków końca linii:

```php
$body = json_decode(file_get_contents('php://input'), true);

$json = json_encode(
    $body['response'] ?? null,
    JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_LINE_TERMINATORS
);
$expected = hash_hmac('sha256', sprintf('%s::%s:', $appId, $json), $appSecret);

if (!is_string($body['signature'] ?? null) || !hash_equals($expected, $body['signature'])) {
    http_response_code(400);   // niezgodny podpis — nie przetwarzaj
    exit;
}
```

Flagi kodowania są istotne: bez nich polskie znaki i ukośniki zostaną
zakodowane inaczej i podpis nigdy się nie zgodzi.

### Statusy webhooków

Webhooki mają własny zestaw statusów. Prawa kolumna to najbliższy
[status odpytywania](#statusy-odpytywania).

| Status webhooka | Znaczenie | Odpowiednik w odpytywaniu |
|---|---|---|
| `NEW` | Zamówienie zarejestrowane | `NEW` |
| `READY_TO_SEND` | Gotowa do nadania | `NEW` |
| `ADVISING` | — | `NEW` |
| `POSTED` | Nadana / przekazana przewoźnikowi | `PICKUP` |
| `ON_THE_WAY` | W drodze do odbiorcy | `IN_TRANSIT` |
| `OUT_FOR_DELIVERY` | Przekazana kurierowi do doręczenia | `IN_TRANSIT` |
| `READY_TO_PICKUP` | Gotowa do odbioru w punkcie | `IN_TRANSIT` |
| `DELIVERED` | Doręczona | `DELIVERED` |
| `WAITING_FOR_PAYOUT` | Doręczona, pobranie czeka na wypłatę | `DELIVERED` |
| `SENT` | Pobranie wypłacone | `DELIVERED` |
| `AVIZO` | Przesyłka awizowana | `EXCEPTION` |
| `REMINDER_SENT` | Wysłano przypomnienie do odbiorcy | `EXCEPTION` |
| `OTHER` | Nierozpoznany status przewoźnika | `EXCEPTION` |
| `UNKNOWN` | Status nieznany | `EXCEPTION` |
| `PICKUP_EXPIRED` | Nieodebrana w terminie | `RETURNED_TO_SHIPPER` |
| `RETURNED` | Zwrócona do nadawcy | `RETURNED_TO_SHIPPER` |
| `CANCELED` | Anulowana | `CANCELLED` |
| `CLAIMED` | Zgłoszono reklamację | `CORRECTION` |

Uwaga na pisownię: webhook wysyła `CANCELED` (jedno L), odpytywanie zwraca
`CANCELLED`. A `SENT` oznacza **wypłatę pobrania**, nie nadanie paczki.

### Warto wiedzieć

- **Odpowiadaj HTTP 200.** Dopóki endpoint nie zwróci 200, platforma
  ponawia doręczenie notyfikacji; po 200 kolejna przyjdzie przy następnej
  zmianie statusu. Handler powinien być idempotentny.
- Push tracking jest rejestrowany tylko dla obsługiwanych przewoźników;
  `statuses` może być pustą tablicą — potwierdź ją kodem 200 i nic więcej.
- Znaczniki czasu mają format `Y-m-d\TH:i:s` **bez offsetu, w czasie
  Europe/Warsaw**.
