# Vervoerderslabels

Een vervoerderszending kopen: methoden listen → tarief opvragen → het label aanmaken → de PDF downloaden → tracken → events ontvangen → annuleren (of de dag afsluiten).

Dit handboek documenteert uitsluitend de volgende bewerkingen. Voer ze in volgorde uit.

Vervang `YOUR_HOST`, `ACCESS_TOKEN` en `shipping_method` door de uwe. Methode-id’s verschillen per account — hardcode ze nooit.

## 1. Aanmelden

```bash
curl -X POST https://YOUR_HOST/api/v1/user/login \
  -H "Content-Type: application/json" \
  -d '{"email":"you@example.com","password":"your_password"}'
```

```
Authorization: Bearer ACCESS_TOKEN
```

GraphQL gebruikt dezelfde header op `POST /api/graphql`.

[REST-handboek](/api/documentation#/paths/v1-user-login/post) · [GraphQL-handboek](/api/graphql/documentation#/user/userLogin)

**Verificatie:** login geeft `access_token` terug. Een latere aanroep zonder token geeft `401`.

## 2. Verzendmethoden listen

**REST:** `POST /api/v1/labelservice/getShippingMethodList` — [REST-handboek](/api/documentation#/paths/v1-labelservice-getShippingMethodList/post)

```bash
curl -X POST https://YOUR_HOST/api/v1/labelservice/getShippingMethodList \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"detail": true}'
```

Elke rij heeft:

| Veld | Gebruik |
|---|---|
| `id` | `shipping_method` in elke latere aanroep |
| `name` | Weergavenaam |
| `unique_identifier` | Stabiele code |
| `options.signature_option` | Handtekening beschikbaar |
| `options.insurance_option` | Verzekering beschikbaar |
| `options.multi_package` | Meer dan één stuk |

**GraphQL:** `labelserviceGetShippingMethodList` ([GraphQL-handboek](/api/graphql/documentation#/labelservice/labelserviceGetShippingMethodList)) (JSON-scalar).

**Verificatie:** de lijst is niet leeg. U hebt één `id` gekozen en weet of die methode handtekening, verzekering en meerdere pakketten toestaat. Een lege lijst betekent dat er geen methode op het account is ingeschakeld.

## 3. Tarief opvragen

Droogloop. De vervoerder wordt om een prijs gevraagd; er wordt niets geboekt. De body heeft dezelfde vorm als bij aanmaken. `shipping_method` is verplicht.

**REST:** `POST /api/v1/labelservice/rate` — [REST-handboek](/api/documentation#/paths/v1-labelservice-rate/post)

```bash
curl -X POST https://YOUR_HOST/api/v1/labelservice/rate \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "shipping_method": 59,
    "name": "Jane Recipient",
    "telephone": "5555555555",
    "email": "jane@example.com",
    "address_1": "6701 RUE HADLEY",
    "city": "Montreal",
    "province": "QC",
    "postcode": "H4E3R3",
    "country": "CA",
    "weight": 1,
    "length": 30,
    "width": 20,
    "height": 10,
    "dimension_unit": 2,
    "weight_unit": 2,
    "signature_option": 0,
    "insurance_option": 0,
    "package_type": "parcel",
    "ref": "DEV-LABEL-001",
    "sender_name": "Acme Warehouse",
    "sender_telephone": "5555555555",
    "sender_address_1": "2667 boul des sources",
    "sender_city": "Pointe-Claire",
    "sender_province": "QC",
    "sender_postcode": "H9S2H9",
    "sender_country": "CA"
  }'
```

`weight_unit`: `1` g, `2` kg, `3` oz, `4` lb. `dimension_unit`: `1` mm, `2` cm, `3` m, `4` in.

Voor meer dan één stuk stuurt u `packages: [{ ref, weight, length, width, height, weight_unit, dimension_unit }]`. `shipping_from` (adresboek-id) of `shipping_from_code` kan het `sender_*`-blok vervangen.

**GraphQL:** `labelserviceRate` ([GraphQL-handboek](/api/graphql/documentation#/labelservice/labelserviceRate)).

**Verificatie:** `result` is true en u hebt een prijs (en transittijden, als de vervoerder die stuurt). Is er geen tarief, corrigeer bestemming / pakket / methode **voor** u aanmaakt.

## 4. Het label aanmaken

Dit boekt de zending bij de vervoerder.

**REST:** `POST /api/v1/labelservice/submitOrder` — [REST-handboek](/api/documentation#/paths/v1-labelservice-submitOrder/post)

Zelfde body als stap 3. Stuur `Idempotency-Key`.

```bash
curl -X POST https://YOUR_HOST/api/v1/labelservice/submitOrder \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: dev-label-001" \
  -d '{
    "shipping_method": 59,
    "name": "Jane Recipient",
    "telephone": "5555555555",
    "email": "jane@example.com",
    "address_1": "6701 RUE HADLEY",
    "city": "Montreal",
    "province": "QC",
    "postcode": "H4E3R3",
    "country": "CA",
    "weight": 1,
    "length": 30,
    "width": 20,
    "height": 10,
    "dimension_unit": 2,
    "weight_unit": 2,
    "signature_option": 0,
    "insurance_option": 0,
    "package_type": "parcel",
    "ref": "DEV-LABEL-001",
    "sender_name": "Acme Warehouse",
    "sender_telephone": "5555555555",
    "sender_address_1": "2667 boul des sources",
    "sender_city": "Pointe-Claire",
    "sender_province": "QC",
    "sender_postcode": "H9S2H9",
    "sender_country": "CA"
  }'
```

**GraphQL:** `labelserviceSubmitOrder` ([GraphQL-handboek](/api/graphql/documentation#/labelservice/labelserviceSubmitOrder)).

```json
{
  "result": true,
  "id": 12345,
  "shipping_price": "12.50",
  "tracking_numbers": ["1Z999AA10123456784"],
  "external_id": "EXT_12345"
}
```

| Veld | Gebruik |
|---|---|
| `id` | Superroute-order-id — download en annuleren |
| `tracking_numbers` | Geef deze aan de koper; tracking accepteert ze |
| `external_id` | Vervoerderszending-id |
| `shipping_price` | In rekening gebracht bedrag |

**Verificatie:** `tracking_numbers` is niet leeg. Bewaar `id` en de nummers. Dezelfde `Idempotency-Key` mag geen tweede label kopen.

## 5. De PDF downloaden

**REST:** `POST /api/v1/labelservice/getShippingLabel` — [REST-handboek](/api/documentation#/paths/v1-labelservice-getShippingLabel/post)

```bash
curl -X POST https://YOUR_HOST/api/v1/labelservice/getShippingLabel \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "id": "12345",
    "type": "ORDER_ID",
    "base64": 1
  }'
```

`type` mag `ORDER_ID`, `TRACKING_NUMBER` of `REF` zijn. `base64: 0` streamt een PDF. Dit is het **officiële vervoerderslabel**. Het aantal stuks ligt vast door de boeking.

**GraphQL:** `labelserviceGetShippingLabel` ([GraphQL-handboek](/api/graphql/documentation#/labelservice/labelserviceGetShippingLabel)).

**Verificatie:** de PDF opent en toont de barcode / tracking van de vervoerder uit stap 4. Print één testkopie en gooi die weg — geef geen testlabel aan een vervoerder.

## 6. Volgen

Geen token. Gebruik een nummer uit `tracking_numbers`:

```bash
curl https://YOUR_HOST/api/v1/tracking/1Z999AA10123456784
```

[REST-handboek](/api/documentation#/operations/getPublicTracking) · [GraphQL-handboek](/api/graphql/documentation#/tracking/trackingPublic)

**GraphQL:**

```graphql
query {
  trackingPublic(trackingNumber: "1Z999AA10123456784") {
    result
    deliveried
    is_third_party_tracking
    data {
      tracking_event_status_id
      tracking_event_key
      description
      updated_at_localized
    }
    proofs { file_id type full_url signed_url }
  }
}
```

`is_third_party_tracking` is true wanneer events van de vervoerder komen. Vertak op `tracking_event_status_id` / `tracking_event_key`, niet op `description`. Het nieuwste event staat eerst in `data`. Vroege events kunnen nog «informatie doorgegeven» zijn tot de vervoerder het pakket scant. `500` is bezorgd; `proofs[]` kan dan handtekening (`type` `1`) of foto (`type` `2`) bevatten.

**Verificatie:** de opzoeking geeft de zending terug die u zojuist hebt aangemaakt. Een onbekend nummer is `result: false` / 404.

## 7. Gebeurtenismeldingen configureren

| Instelling | Event | Wanneer |
|---|---|---|
| `order_create_webhook_url` | `order.created` | `id` en vervoerderstrackingnummers bewaren |
| `tracking_event_webhook_url` | `tracking.event` | Scans van de vervoerder, onderweg, bezorgd |
| `order_status_change_webhook_url` | `order.status_change` | Status in uw systeem |
| `order_cancel_failed_webhook_url` | `order.cancel_failed` | Annuleren geweigerd omdat de vervoerder het pakket al heeft |

**REST:** `PUT /api/v1/webhook-settings` — [REST-handboek](/api/documentation#/paths/v1-webhook-settings/put)

**GraphQL:** `webhookSettingsUpdate` ([GraphQL-handboek](/api/graphql/documentation#/webhooks/webhookSettingsUpdate)).

```bash
curl -X PUT https://YOUR_HOST/api/v1/webhook-settings \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "order_create_webhook_url": "https://erp.example.com/hooks/superroute",
    "tracking_event_webhook_url": "https://erp.example.com/hooks/superroute",
    "webhook_sign_secret": "YOUR_LONG_RANDOM_SECRET",
    "webhook_verify_ssl": 1
  }'
```

Verifieer **v2** over de ruwe body: `HMAC_SHA256(timestamp + "." + raw_body, secret)` tegen `X-Webhook-Signature-V2`. Dedupliceer op `X-Webhook-Event-Id`. Antwoord **2xx binnen 3 seconden**.

```php
$raw = file_get_contents('php://input');
$ts  = $_SERVER['HTTP_X_WEBHOOK_TIMESTAMP'] ?? '';
$sig = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE_V2'] ?? '';
$expected = hash_hmac('sha256', $ts . '.' . $raw, $sharedSecret);
if (!hash_equals($expected, $sig)) {
    http_response_code(401);
    exit;
}
```

**Verificatie:** één test-`submitOrder` produceert `order.created` met die `tracking_numbers`. Een verkeerd secret is `401` van uw ontvanger.

## 8. Annuleren

Alleen zolang de vervoerder het nog toestaat (meestal vóór ophalen).

**REST:** `POST /api/v1/labelservice/cancelShippingLabel` — [REST-handboek](/api/documentation#/paths/v1-labelservice-cancelShippingLabel/post)

```bash
curl -X POST https://YOUR_HOST/api/v1/labelservice/cancelShippingLabel \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"id":"12345","type":"ORDER_ID"}'
```

**GraphQL:** `labelserviceCancelShippingLabel` ([GraphQL-handboek](/api/graphql/documentation#/labelservice/labelserviceCancelShippingLabel)).

Een vervoerder die het pakket al heeft, weigert — dat is `order.cancel_failed`.

**Verificatie:** een tweede annulering is veilig. Tracking behandelt de zending niet meer als actief.

## 9. Einde dag (alleen als deze methode het vereist)

Sommige vervoerders hebben een dagelijks manifest nodig.

**REST:** `POST /api/v1/labelservice/endofday` — [REST-handboek](/api/documentation#/paths/v1-labelservice-endofday/post)

```bash
curl -X POST https://YOUR_HOST/api/v1/labelservice/endofday \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"shipping_method": 59}'
```

**GraphQL:** `labelserviceEndofday` ([GraphQL-handboek](/api/graphql/documentation#/labelservice/labelserviceEndofday)).

Roep het één keer aan na het laatste label van de verzenddag. Sla deze stap over als de methode uit stap 2 dat niet vereist.

**Verificatie:** de succesbody (of de bevestiging van de vervoerder) somt de labels van vandaag op. Voer dit eerst uit op een testmethode.

## Testlijst

Gebruik een bestemming die u beheert en een methode die geannuleerd kan worden:

- [ ] Methodenlijst is niet leeg; u hebt één `id` vastgelegd.
- [ ] Tarief geeft een prijs terug voor die methode en bestemming.
- [ ] Submit geeft `tracking_numbers`; dezelfde `Idempotency-Key` koopt geen tweede label.
- [ ] De label-PDF opent en toont het trackingnummer van de vervoerder.
- [ ] Publieke tracking vindt de zending op dat nummer.
- [ ] `order.created` komt binnen; v2-handtekening verifieert.
- [ ] Annuleren lukt, **of** u hebt bevestigd dat deze methode na boeken niet geannuleerd kan worden.
- [ ] Als de methode einde-dag nodig heeft, loopt een testrun zonder fout.
