Naar inhoud
retourly
Gratis starten

API en webhooks

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

  1. Ga naar API en webhooks en klik op Token aanmaken. Alleen een eigenaar kan dit.
  2. 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.
  3. 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.