Koppel je eigen systeem aan Retourly: haal retouren op, keur ze goed of wijs ze af, plaats notities, en laat Retourly melden wanneer er iets met een retour gebeurt. Alles staat onder API en webhooks in het menu.
Token aanmaken
- Ga naar API en webhooks en klik op Token aanmaken. Alleen een eigenaar kan dit.
- Geef de koppeling een naam en kies de rechten:
- Beheerder: retouren lezen, goedkeuren, afwijzen, notities plaatsen (ook voor de klant) en regels en instellingen lezen.
- Magazijn: retouren lezen, goedkeuren en interne notities plaatsen.
- Kopieer het token meteen. Je ziet het één keer; daarna staat in de lijst alleen nog het begin ervan, plus wanneer het voor het laatst is gebruikt.
Per koppeling één token, dan kun je er één intrekken zonder de rest te raken. Klik op Intrekken en de koppeling heeft direct geen toegang meer.
Aanroepen
Basis-URL: https://retourly.nl/api/v1. Elk verzoek draagt het token mee:
Authorization: Bearer rtl_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Accept: application/json
Je ziet uitsluitend je eigen retouren; een id van een andere shop bestaat voor jouw token niet.
GET /returns
Retouren van je shop, nieuwste eerst. Filters (allemaal optioneel): status (één code, meerdere gescheiden door een komma, of status[]=), from en to (YYYY-MM-DD, op aanmaakdatum), order_number, customer_email, tag, page en per_page (maximaal 100, standaard 25).
GET /api/v1/returns?status=manual,received&per_page=2
{
"data": [
{
"id": "9b1f7d2c-5a4e-4c3b-8f10-2d9a5c7e1b33",
"order_number": "1000042",
"status": "manual",
"outcome": null,
"source": "portal",
"currency": "EUR",
"return_total_cents": 7995,
"lines_count": 1,
"tags": ["Beschadigd"],
"exchange_wish": false,
"flagged": false,
"test": false,
"customer": {"name": "Jan Jansen", "email": "jan@example.com", "country": "NL"},
"submitted_at": "2026-09-10T09:12:00+00:00",
"created_at": "2026-09-10T09:11:58+00:00"
}
],
"meta": {"page": 1, "per_page": 2, "total": 1, "last_page": 1}
}
Bedragen staan altijd in hele centen, met de valuta in currency. test is true bij een testretour die je zelf met de knop Testretour hebt aangemeld: die staat op een echte bestelling, maar er is geen label gekocht en er gaat geen geld. Sla hem in je eigen systeem over.
GET /returns/{id}
Dezelfde retour met de artikelregels, het label en het volledige verloop:
{
"data": {
"id": "9b1f7d2c-5a4e-4c3b-8f10-2d9a5c7e1b33",
"order_number": "1000042",
"status": "received",
"order_total_cents": 12990,
"refundable_total_cents": 7995,
"deducted_cents": 0,
"lines": [
{
"id": 41,
"sku": "JEANS-32",
"name": "Slim fit jeans, Maat: 32",
"quantity": 1,
"unit_price_cents": 7995,
"reason_code": "too_small",
"reason_text": null,
"preference": "refund",
"exchange_wish": null,
"deduction_cents": 0,
"refunded_at": null,
"photos_count": 2
}
],
"label": {"carrier": "postnl", "tracking_number": "3SABC123", "tracking_url": "https://...", "status": "delivered"},
"timeline": [
{"type": "submitted", "status": "submitted", "actor": "customer", "message": null, "occurred_at": "2026-09-10T09:12:00+00:00"},
{"type": "received", "status": "received", "actor": "wms", "message": null, "occurred_at": "2026-09-12T08:03:11+00:00"}
]
}
}
In timeline staat per stap een grove soort actor (customer, admin, support, api, wms, system), nooit een naam of een id. support is een medewerker van Retourly die de stap zette terwijl hij je hielp, dus niet iemand uit je eigen team. message is alleen tekst die een mens schreef: een notitie, of bij een afwijzing de reden die de klant krijgt. Wijst een retourregel de retour af, dan staat daar de boodschap van die regel, niet de interne afweging van de regels.
POST /returns/{id}/approve
Keurt een retour goed, precies zoals de knop in het beheer: de retourregels bepalen daarna het label en de klant krijgt bericht.
POST /api/v1/returns/{id}/approve
{"outcome": "free_label"}
outcome is free_label (standaard) of paid_label. Het antwoord is de retour met zijn nieuwe status.
POST /returns/{id}/reject
POST /api/v1/returns/{id}/reject
{"reason": "Buiten de retourtermijn"}
reason is verplicht (maximaal 500 tekens) en komt in de afwijsmail en op de statuspagina.
POST /returns/{id}/notes
POST /api/v1/returns/{id}/notes
{"text": "Klant gebeld, stuurt maandag terug", "audience": "internal"}
audience is internal (standaard) of customer. Een notitie voor de klant verschijnt op de statuspagina en gaat per mail, en vraagt een token met beheerdersrechten. Bij een bol-retour kan dat niet: bol informeert die klant zelf.
GET /rules en GET /settings
GET /rules geeft je retourregels in de volgorde waarin ze gelden, met hun conditiegroepen en uitkomst. GET /settings geeft je retourtermijn, je retouradressen, contactgegevens, de prijs van een betaald label, je retourredenen, de talen van je portaal (shop.locale is de standaardtaal, shop.locales de hele set) en je tags. Beide vragen een token met beheerdersrechten. Wachtwoorden, sleutels, tokens en webhook-secrets staan nooit in een antwoord.
Geen geld in deze versie
Terugbetalen en tegoed uitgeven zitten niet in v1 van de API. Dat doe je in het beheer of met een automatisering, zodat er geen geld wegloopt door een koppeling die iets dubbel aanroept.
Fouten
Elke fout heeft dezelfde vorm, met een code waar je code op kan beslissen:
{"error": {"code": "validation_failed", "message": "The request does not hold up; see fields.", "fields": {"reason": ["The reason field is required."]}}}
| Code | HTTP | Betekenis |
|---|---|---|
unauthorized |
401 | Geen, onbekend of ingetrokken token |
forbidden |
403 | Het token mag dit niet |
not_found |
404 | Bestaat niet (of niet binnen jouw shop) |
method_not_allowed |
405 | Verkeerde methode op dit adres |
validation_failed |
422 | Verzoek klopt niet; zie fields |
invalid_state |
409 | De retour staat niet in een stand die dit toelaat |
rate_limited |
429 | Te veel verzoeken |
server_error |
500 | Het ging bij ons mis |
Grenzen
- 60 verzoeken per minuut per token. Daarboven volgt 429 met een
Retry-After-header. - Maximaal 100 retouren per pagina.
Webhooks
Voeg onder API en webhooks een webhook toe: het adres waar je meldingen wilt ontvangen en de gebeurtenissen die je wilt. Retourly maakt er een secret bij. Per webhook zie je in de lijst de laatste melding, dus je merkt het als je endpoint eruit ligt.
| Gebeurtenis | Wanneer |
|---|---|
return.created |
De klant heeft een retour aangemeld |
return.approved |
De retour is goedgekeurd |
return.rejected |
De retour is afgewezen |
return.label_created |
Het retourlabel is aangemaakt |
return.received |
Het pakket is binnen |
return.refunded |
De terugbetaling is gedaan |
return.credited |
Er is tegoed uitgegeven |
return.exchanged |
De retour is omgeruild |
return.closed |
De retour is afgehandeld |
return.customer_note |
Er is een notitie voor de klant geplaatst |
Wij doen een POST met deze headers:
X-Retourly-Event: return.approved
X-Retourly-Delivery: 0d7a1f4e-2c88-4a1d-9f2b-6f1a0f1b9c21
X-Retourly-Signature: 6c6f1e...
Content-Type: application/json
En deze body:
{
"event": "return.approved",
"delivery_id": "0d7a1f4e-2c88-4a1d-9f2b-6f1a0f1b9c21",
"occurred_at": "2026-09-12T08:05:00+00:00",
"message": null,
"return": {"id": "9b1f...", "order_number": "1000042", "status": "approved", "test": false, "lines": [{"sku": "JEANS-32", "quantity": 1}]}
}
return heeft dezelfde vorm als in GET /returns, dus ook test: boek of mail nooit op een melding met test: true. message bevat bij return.customer_note de tekst voor de klant en bij return.rejected de reden die de klant krijgt: de tekst die een mens typte, of de boodschap van de retourregel die afwees. occurred_at is het moment van de gebeurtenis zelf, ook als we de melding later of opnieuw versturen.
Handtekening controleren. X-Retourly-Signature is een HMAC-SHA256 over de onbewerkte body, met het secret van die webhook als sleutel. Vergelijk in vaste tijd:
$expected = hash_hmac('sha256', $rawBody, $secret);
if (! hash_equals($expected, $request->header('X-Retourly-Signature'))) {
abort(401);
}
Je endpoint moet een publiek adres zijn, ook als je ons doorstuurt: een doorverwijzing naar een adres binnen een netwerk volgen we niet. Antwoord met een 2xx-status. Lukt dat niet, dan proberen we het een paar keer opnieuw met steeds meer tijd ertussen; daarna stopt die melding en zie je dat terug bij de webhook. Gebruik delivery_id om dezelfde melding niet twee keer te verwerken, en behandel de body als een melding, niet als de waarheid: haal bij twijfel de retour op met GET /returns/{id}.
Nog niet gevonden wat je zocht? Mail retouren@retourly.nl, dan helpen we je verder.