Custom shipping provider - Zendingstatussen ontvangen via webhook

Updated by Bram

Deze methode is geschikt voor programmeurs en is geen kant & klare oplossing. Schakel een programmeur in om deze koppeling te realiseren.

Uitleg

Met een webhook stuurt jouw verzendpartij de status van een zending terug naar GoedGepickt. De status en de historie op de zending zijn dan altijd actueel zonder dat je ze handmatig bijwerkt.

Je hebt een werkende Custom Shipping Provider koppeling nodig. Zie Custom Shipping Provider: eigen/andere verzendmethoden koppelen.

Let op: Updates worden alleen verwerkt als het API geheim in de koppeling is ingevuld. Zonder API geheim geeft de webhook altijd een 401 terug.
Let op: Het API geheim wordt ook gebruikt voor de X-Signature header bij het aanmaken van een zending. Vul je nu voor het eerst een API geheim in? Controleer dan bij je verzendpartij of het aanmaken van zendingen goed blijft werken.

Koppeling opstarten

In GoedGepickt navigeer je naar Instellingen -> Verzendproviders en open je de Custom Shipping Provider koppeling.

  • Controleer of het API geheim is ingevuld. Vul het in en sla op als dat nog niet zo is.
  • Kopieer de Zending status webhook URL. Je kan dit veld niet aanpassen. Het staat pas in de koppeling nadat je hebt opgeslagen.
  • Geef de URL en het API geheim aan je verzendpartij.

De URL heeft deze vorm:

https://<jouw-domein>/webhook/custom/handle-webhook/<uuid>
Webhook URL in het zending-request

Bij het aanmaken van een zending sturen we de webhook URL van de koppeling mee in het veld status_webhook_url. Een partij met meerdere GoedGepickt-klanten hoeft zo geen URL per klant te bewaren. Alleen het API geheim per klant is nodig. Het veld is optioneel. Negeer je het dan merk je er niets van. We sturen het ook mee als er geen API geheim is ingesteld.

Het request opbouwen

Stuur een POST-request met Content-Type: application/json en deze twee headers:

  • X-GoedGepickt-Timestamp (verplicht): de huidige unix-tijd in seconden. Bijvoorbeeld 1760000000.
  • X-GoedGepickt-Signature (verplicht): sha256= gevolgd door de HMAC-SHA256 in kleine letters hex.
De handtekening berekenen

Bereken de HMAC-SHA256 over de timestamp, een punt en de ruwe body. Gebruik het API geheim als sleutel. Gebruik de body precies zoals je hem verstuurt. Een extra spatie of regeleinde geeft een andere handtekening.

<?php
$apiSecret = 'jouw-api-geheim';
$timestamp = (string) time();
$body = json_encode($payload);
$signature = 'sha256=' . hash_hmac('sha256', $timestamp . '.' . $body, $apiSecret);

$headers = [
'Content-Type: application/json',
'X-GoedGepickt-Timestamp: ' . $timestamp,
'X-GoedGepickt-Signature: ' . $signature,
];

De timestamp mag maximaal 5 minuten afwijken van de tijd bij GoedGepickt. Dat geldt vooruit en achteruit. Zo kan niemand een oud request opnieuw afspelen.

Dit is een andere handtekening dan de X-Signature header die wij meesturen bij het aanmaken van een zending (md5 van de bestelling UUID en het API geheim).

De body
{
"updates": [
{
"trackingnumber": "3SABCD1234567",
"status": "parcel_en_route",
"occurred_at": "2026-10-05T10:15:00+02:00",
"description": "Aangekomen in sorteercentrum",
"location": "Nieuwegein"
}
]
}
  • updates (verplicht): stuur één update per request.
  • trackingnumber (verplicht): het trackingnummer dat je bij het aanmaken van de zending hebt teruggegeven. Bij multi-collo heeft elk collo een eigen trackingnummer. Elk collo krijgt dus een eigen request.
  • status (verplicht): een van de statussen hieronder.
  • occurred_at (verplicht): het moment van de statuswijziging in ISO 8601 met tijdzone. Bijvoorbeeld 2026-10-05T10:15:00+02:00 of 2026-10-05T08:15:00Z.
  • description: toelichting. Maximaal 255 tekens.
  • location: plaats of locatie. Maximaal 255 tekens.

De velden description, location en occurred_at worden bij de statuswijziging opgeslagen. In het zendingscherm zie je nu alleen de status in de historie.

Statussen

Stuur alleen deze waarden. Je hoeft niet alle statussen te sturen. Stuur alleen wat je vervoerder meldt.

  • announced: aangemeld bij de vervoerder. Zichtbaar als "Aangemeld".
  • parcel_en_route: onderweg. Zichtbaar als "Zending onderweg".
  • in_distribution: in bezorging. Zichtbaar als "In distributie".
  • delivery_attempt_failed: bezorgpoging mislukt. Zichtbaar als "Kon niet bezorgen".
  • delivered_ready_for_pickup: klaar bij het servicepunt. Zichtbaar als "Afgeleverd bij ophaallocatie".
  • picked_up_by_customer: opgehaald bij het servicepunt door de ontvanger. Zichtbaar als "Opgehaald bij ophaallocatie".
  • delivered: bezorgd. Zichtbaar als "Afgeleverd".
  • returned_to_sender: retour afzender. Zichtbaar als "Retour naar afzender".
  • cancelled: geannuleerd. Zichtbaar als "Geannuleerd".

De statussen delivered en picked_up_by_customer tellen als afgeleverd. De zending staat dan onder het filter afgeleverd.

Alleen vooruit

Een status gaat alleen vooruit in deze volgorde:

  1. announced
  2. parcel_en_route
  3. in_distribution en delivery_attempt_failed. Deze staan op gelijke hoogte. Zo kunnen ze elkaar bij een tweede bezorgpoging afwisselen.
  4. delivered_ready_for_pickup
  5. picked_up_by_customer, delivered, returned_to_sender en cancelled. Dit zijn eindstatussen.

Een update die lager staat dan de huidige status wordt genegeerd. Na een eindstatus worden alle updates genegeerd. Stuur je dezelfde status nog een keer dan komt er geen dubbele regel in de historie.

Let op: We volgen de volgorde waarin updates binnenkomen en niet het tijdstip in occurred_at. Stuur updates dus in de volgorde waarin ze gebeurd zijn.

Antwoord van GoedGepickt

Bij een geldige handtekening en een geldige opbouw is het antwoord altijd 200 met een resultaat per update. Voorbeeld:

{
"results": [
{ "trackingnumber": "3SABCD1234567", "result": "processed" }
]
}

Het veld result heeft een van deze waarden:

  • processed: de status en de historie zijn bijgewerkt.
  • ignored: de update is herkend maar bewust niet verwerkt.
  • error: de update kon niet worden verwerkt.

Bij ignored en error staat er ook een reason:

  • not_found (error): geen zending met dit trackingnummer bij deze koppeling. Of de zending is verwijderd.
  • invalid_status (error): de status staat niet in de lijst hierboven.
  • invalid_occurred_at (error): occurred_at ontbreekt of is geen geldige datum met tijdzone.
  • processing_failed (error): tijdelijke fout bij het verwerken. Probeer het later opnieuw.
  • status_not_forward (ignored): de status staat lager dan de huidige status van de zending.
  • already_final (ignored): de zending heeft al een eindstatus.
  • duplicate_status (ignored): de zending heeft deze status al.
Let op: Ook een error komt met statuscode 200 terug. Kijk dus altijd naar het veld result en niet alleen naar de statuscode.

Foutmeldingen

Bij deze fouten verwerken we niets:

  • 401: er is geen API geheim ingesteld. Of de handtekening of timestamp ontbreekt of klopt niet. Of de timestamp wijkt meer dan 5 minuten af.
  • 422: de body is geen geldige JSON. Of updates ontbreekt of is geen lijst. Of er zijn meer dan 1 updates. Of trackingnumber ontbreekt. Of description of location is langer dan 255 tekens.

Krijg je steeds een 401? Loop dan dit na:

  • Staat het API geheim in de koppeling?
  • Reken je de handtekening over de ruwe body met de timestamp uit dezelfde header en een punt ertussen?
  • Klopt de klok van je server?

Testen

Er is geen testknop. Maak een zending aan via de koppeling. Stuur daarna een update met het trackingnummer van die zending. Het resultaat zie je direct in het antwoord. De status en de historie zie je op de zending in GoedGepickt.

Vragen?

Kom je er niet uit? Neem dan contact met ons op.


Hoe vond je dit artikel?