For developers
API documentation
Alles op één adres, achter één sleutel. Je winkelsysteem levert bestellingen aan, je haalt je eigen reviews en cijfers weer op, en je laat ons je server een seintje geven zodra er iets verandert.
Updated 26 August 2026
This guide is written in Dutch. The endpoints, field names and error codes are English and apply word for word to everyone.
Deze pagina is geschreven voor de ontwikkelaar die de koppeling bouwt. De voorbeelden zijn kant en klaar over te nemen.
Het basisadres is https://klantreviews.online/api/v1. Alles gaat over HTTPS, alles spreekt JSON, en elk antwoord draagt het veld ok zodat je in één blik ziet of het gelukt is.
Aanmelden met een sleutel
Elke aanroep draagt een API-sleutel in de kopregel Authorization, met Bearer ervoor.
curl https://klantreviews.online/api/v1/reviews \
-H "Authorization: Bearer kro_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
Een sleutel maak je aan in je account, onder Automatisch uitnodigen. Hij hoort bij één bedrijf en is één keer te zien op het moment dat je hem aanmaakt. Daarna bewaren wij alleen de hash, dus wij kunnen hem ook niet meer voor je opzoeken. Raakt hij kwijt, dan trek je hem in en maak je een nieuwe.
Maak er één per systeem dat aanroept. Dan kun je er later één intrekken zonder dat de rest stilvalt, en is in je logboek te zien welk systeem wat deed.
Wat een sleutel mag
Bij elke sleutel kies je welke rechten hij draagt. Een sleutel die in een rapportagescript staat hoeft geen bestellingen te kunnen aanmaken.
| Recht | Waarvoor |
|---|---|
orders:write |
Bestellingen aanleveren bij de orderingang |
invites:write |
Verzamelsleutels maken voor de widget |
reviews:read |
Je eigen gepubliceerde reviews en cijfers teruglezen |
De rechten van een bestaande sleutel pas je aan in datzelfde scherm, zonder dat je hem hoeft te vervangen. Elke wijziging komt in het logboek van je bedrijf te staan.
Als het misgaat bij de deur
Een sleutel die wij niet herkennen, die is ingetrokken of die bij een bedrijf hoort waarvan de toegang stilstaat, levert altijd hetzelfde antwoord op.
HTTP 401
{ "ok": false, "reason": "unauthorized" }
Klopt de sleutel wel en draagt hij het recht niet dat deze route vraagt, dan staat in het antwoord welk recht er ontbreekt.
HTTP 403
{ "ok": false, "reason": "missing_scope", "scope": "reviews:read" }
Dat is met opzet een 403 en geen 404. Je pad klopt en je sleutel bestaat; wat eraan ontbreekt is een vinkje in je eigen account.
Wat je pakket toelaat
De API hoort bij de betaalde pakketten. Reviews verzamelen en tonen zit in het gratis pakket, en een machine die ze gestructureerd ophaalt of bestellingen aanlevert valt onder automatisering.
Staat je pakket het niet toe, dan zegt het antwoord welk pakket je nu hebt, zodat je dat in je eigen logboek terugziet.
HTTP 403
{ "ok": false, "reason": "plan_limit", "plan": "free" }
Draai je WooCommerce, PrestaShop, Magento of Shopware? Dan hoef je niets te bouwen
Voor allebei is er een plug-in die dit hele hoofdstuk voor je doet:
- WooCommerce — installeren via Plugins, Nieuwe plug-in, Plug-in uploaden.
- PrestaShop — installeren via Modules, Module uploaden.
- Magento 2 — uitpakken in
app/code/Klantreviews/Onlineenbin/magento setup:upgradedraaien. - Shopware 6 — uitpakken in
custom/plugins/KlantreviewsOnlineenbin/console plugin:install --activatedraaien.
Daarna plak je een sleutel en is het klaar.
Wat hij doorgeeft is precies wat hieronder staat, en niets meer: het bestelnummer, het e-mailadres, de voornaam, het moment van bestellen, en de artikelen als je dat aanzet. Geen adres, geen bedrag, geen betaalgegevens.
Voor Shopify is er niets te installeren: dat systeem laat geen vreemde code in een winkel toe. Daar zet je in je eigen beheer een melding klaar naar ons adres; de stappen staan in je dashboard bij Automatisch uitnodigen.
Plug-ins voor de overige winkelsystemen volgen. Tot die er zijn is de weg hieronder de weg, en die is voor elk systeem hetzelfde.
Bestellingen aanleveren
POST /api/v1/orders
Stuur elke afgeronde bestelling door. Wij zetten de uitnodiging klaar en versturen hem zodra de wachttijd van je bedrijf voorbij is. Er gaat op het moment van deze aanroep nooit post de deur uit.
Vraagt om orders:write.
curl -X POST https://klantreviews.online/api/v1/orders \
-H "Authorization: Bearer kro_live_..." \
-H "Content-Type: application/json" \
-d '{
"orderReference": "WEB-2026-3311",
"email": "klant@voorbeeld.nl",
"name": "Marieke de Groot",
"orderedAt": "2026-08-20T14:05:00+02:00",
"location": "amsterdam-centrum",
"products": [
{ "reference": "SKU-8842", "name": "Wollen trui", "url": "https://voorbeeld.nl/wollen-trui" }
]
}'
| Veld | Verplicht | Betekenis |
|---|---|---|
orderReference |
ja | Je eigen ordernummer. Twee keer dezelfde levert één uitnodiging op. |
email |
ja | Het adres van je klant. |
name |
nee | De naam van je klant, voor de aanhef in de uitnodiging. |
orderedAt |
nee | Wanneer er is besteld, in RFC 3339. Laat je hem weg, dan telt de wachttijd vanaf het moment dat wij de bestelling binnenkrijgen. |
location |
nee | De slug van de vestiging waar deze bestelling vandaan komt. Die staat in je beheerscherm. |
products |
nee | De artikelen uit deze bestelling. Per artikel reference, en verder name, url en imageUrl als je ze hebt. |
Een vestiging of een artikel dat wij niet kennen houdt de bestelling nooit tegen. De uitnodiging vertrekt gewoon, en de review gaat dan over je winkel als geheel.
Er blijft één uitnodiging per bestelling, ook als je tien artikelen meestuurt. Je klant kiest bij het schrijven waar hij het over heeft.
Antwoorden:
HTTP 202
{ "ok": true, "status": "scheduled", "sendAt": "2026-08-27T10:05:00+02:00" }
status |
Wat er is gebeurd |
|---|---|
scheduled |
De uitnodiging staat klaar en vertrekt op het moment in sendAt. |
paused |
De uitnodiging staat klaar. Automatisch uitnodigen staat op dit moment uit. |
duplicate |
Voor dit ordernummer stond er al een uitnodiging. Er is niets veranderd. |
suppressed |
Dit adres staat op je onderdrukkingslijst. Er gaat niets naartoe. |
duplicate en suppressed komen terug met HTTP 200 en niet met een foutcode. Er is niets misgegaan aan jouw kant en er valt niets opnieuw te proberen.
Verzamelen op je eigen bedankpagina
POST /api/v1/collect-tokens
Vraag een korte sleutel aan voor één bezoeker, waarmee de widget op je bedankpagina meteen om een review kan vragen. De sleutel hoort bij één bestelling en vervalt na een etmaal.
Vraagt om invites:write.
curl -X POST https://klantreviews.online/api/v1/collect-tokens \
-H "Authorization: Bearer kro_live_..." \
-H "Content-Type: application/json" \
-d '{ "orderReference": "WEB-2026-3311" }'
HTTP 201
{ "ok": true, "token": "...", "expiresAt": "2026-08-27T12:00:00Z" }
Deze aanroep hoort op je server thuis en niet in de browser van je bezoeker, want je API-sleutel gaat erin mee. De korte sleutel die eruit komt is wel bedoeld voor de browser.
| Antwoord | Betekenis |
|---|---|
HTTP 404 met unknown |
Er staat geen uitnodiging voor dit ordernummer. Controleer of je bestelling is aangekomen. |
HTTP 409 met written |
Deze klant heeft zijn review al geschreven. Er is niets aan de hand. |
HTTP 429 met many |
Er staan er al te veel open voor deze bestelling. Wacht tot de oudste vervalt. |
De widget op je eigen site
Eén regel op je pagina, en er is geen sleutel voor nodig. De gegevens komen van een openbaar adres, dus dit hoort in de browser thuis en niet op je server.
<script src="https://klantreviews.online/widget.js"
data-company="jouw-slug" data-variant="small" async></script>
| Attribuut | Waarvoor |
|---|---|
data-company |
Verplicht. De slug van je bedrijfspagina bij ons. |
data-variant |
micro, button, mini, small, full, panel of collect. Zonder deze staat er small. |
data-theme |
dark voor een donkere voettekst of sectie. |
data-locale |
nl of en. Zonder deze kiest de widget Nederlands. |
data-product |
Je eigen artikelnummer. De widget toont dan het cijfer van dat artikel in plaats van dat van je winkel. Een nummer dat wij niet kennen valt terug op je winkel. |
data-collect |
ask zet het reviewformulier eronder. |
data-collect-token |
De korte sleutel uit /collect-tokens, voor een review met het keurmerk geverifieerd. |
data-schema |
on schrijft de opmaak mee waarmee een zoekmachine sterren bij je resultaat kan tonen. |
data-target |
Een CSS-selector. Zonder deze komt de widget op de plek van het scriptelement. |
Sterren in de zoekresultaten
data-schema="on" werkt alleen samen met data-product, en dat is met opzet.
Voor een artikel mag een winkel zelf de opmaak op zijn productpagina zetten, en dat is wat er dan gebeurt: het cijfer en de reviews die de widget toont, nog eens in de vorm die een zoekmachine leest. Voor je winkel als geheel mag dat niet. Een bedrijf dat op zijn eigen site zijn eigen beoordeling als opmaak neerzet, prijst zichzelf aan, en die opmaak wordt genegeerd of erger. Daarom doet dit attribuut niets zonder een artikelnummer erbij.
De losse reviews gaan alleen mee bij de varianten die ze ook tonen, full en panel. Opmaak over iets dat een bezoeker niet ziet staan is de andere manier om deze regels te overtreden.
Zet dit uit als je webshop zelf al productopmaak plaatst. Twee beschrijvingen van hetzelfde artikel op één pagina is slechter dan één.
Je reviews meenemen
GET /api/v1/companies/{id}/export/portable
Je gepubliceerde reviews in één bestand, met een handtekening van ons eronder. Bedoeld voor de dag dat je naar een ander platform gaat.
Een gewoon exportbestand is tekst: er valt een regel in bij te typen, een cijfer in te veranderen en een slechte review uit te halen, en de ontvanger kan dat niet zien. Dit bestand draagt een Ed25519-handtekening. Wie het krijgt kan met onze openbare sleutel narekenen dat het van ons komt en sindsdien niet is aangeraakt.
{
"ok": true,
"algorithm": "ed25519",
"keyUrl": "https://klantreviews.online/api/v1/public/attestation-key",
"howTo": "base64url-decode het veld document, en reken de handtekening daarover na met de openbare sleutel van keyUrl (Ed25519).",
"signature": "...",
"document": "eyJ2ZXJzaW9uIjox..."
}
Het document staat als base64url in het bestand en niet als JSON in JSON. Een handtekening staat over bytes, en twee programma's die dezelfde JSON opnieuw opschrijven doen dat niet gegarandeerd byte voor byte hetzelfde. Wie base64url decodeert houdt precies de bytes over waar de handtekening over gaat.
In het document staan je bedrijfsgegevens, je cijfer en je gepubliceerde reviews met cijfer, kop, tekst, naam, datum, taal, hoe ze binnenkwamen en het antwoord dat je eronder zette. Adressen, IP-hashes en sleutels staan er niet in, net als bij de gewone export.
De gewone export blijft bestaan en staat op /companies/{id}/export?format=csv of format=json. Die is bedoeld om zelf te lezen; deze is bedoeld om door te geven.
Je reviews teruglezen
GET /api/v1/reviews
Haalt de reviews van je bedrijf op, van oud naar nieuw op het moment van hun laatste wijziging. Dit is de weg om je reviews op je eigen site te tonen of er een rapport van te bouwen.
Vraagt om reviews:read.
curl "https://klantreviews.online/api/v1/reviews?limit=100&updatedSince=2026-08-01T00:00:00Z" \
-H "Authorization: Bearer kro_live_..."
| Parameter | Standaard | Betekenis |
|---|---|---|
limit |
50 | Hoeveel reviews er in dit antwoord gaan. Meer dan 200 levert er 200 op. |
cursor |
leeg | De waarde uit nextCursor van je vorige antwoord. Leeg betekent: begin vooraan. |
updatedSince |
leeg | Alleen wat na dit moment is gewijzigd, in RFC 3339 met tijdzone erin. |
HTTP 200
{
"ok": true,
"reviews": [
{
"id": 4711,
"status": "published",
"updatedAt": "2026-08-21T09:26:53.589Z",
"publishedAt": "2026-08-20T18:02:11Z",
"editedAt": null,
"orderedAt": "2026-08-14T00:00:00Z",
"authorName": "Marieke de Groot",
"score": 9,
"title": "Netjes geleverd",
"content": "De doos stond er de volgende dag al.",
"language": "nl",
"verification": "verified",
"product": { "slug": "wollen-trui", "name": "Wollen trui" },
"location": { "slug": "amsterdam-centrum", "name": "Amsterdam Centrum", "city": "Amsterdam", "closed": false },
"reply": { "content": "Fijn om te horen, Marieke.", "publishedAt": "2026-08-21T09:26:53Z" }
}
],
"nextCursor": "MXwxNzU..."
}
| Veld | Betekenis |
|---|---|
id |
Ons nummer voor deze review. Blijft altijd hetzelfde en is de sleutel waarop je opslaat. |
status |
Waar deze review staat. Zie de tabel verderop. |
updatedAt |
Het moment van de laatste wijziging. Dit is de waarde die je meegeeft aan updatedSince. |
publishedAt |
Wanneer deze review op je bedrijfspagina kwam te staan. |
editedAt |
Wanneer de schrijver hem heeft aangepast, of null. Wat er is veranderd blijft binnen. |
orderedAt |
Wanneer de bestelling werd geplaatst waar deze review over gaat. |
authorName |
De naam die de schrijver zelf opgaf. Bij een geanonimiseerde review staat hier Anoniem. |
score |
Een geheel getal van 1 tot en met 10. |
title |
De kop boven de review. Mag leeg zijn. |
content |
De tekst van de review. |
language |
De taal waarin de review is geschreven, als nl of en. |
verification |
Hoe de review binnenkwam: verified, invited of organic. Dit label heeft geen invloed op de score. |
product |
Het artikel waar deze review over ging, met slug en name. Ontbreekt als de review over je winkel als geheel ging. |
location |
De vestiging waar deze review bij hoort, met slug, name, city en closed. Ontbreekt als de review over het merk ging. |
reply |
Je eigen gepubliceerde antwoord onder deze review, met content en publishedAt. Ontbreekt zolang er geen antwoord staat. |
Reviews die van je site af horen
In de lijst staat ook wat er ooit openbaar was en er nu niet meer staat. Zonder die rijen zou je nooit merken dat een review is weggehaald, en dan blijft hij op je eigen site staan terwijl hij bij ons weg is.
Zo'n rij draagt alleen de drie velden waarmee je hem terugvindt.
{ "id": 3902, "status": "removed", "updatedAt": "2026-08-21T11:04:00Z" }
status |
Wat je ermee doet |
|---|---|
published |
Tonen. Dit is de enige status waarbij de tekst meekomt. |
under_review |
Van je site halen. Er wordt naar deze review gekeken. |
appealed |
Van je site halen. De schrijver heeft bezwaar gemaakt tegen een besluit. |
removed |
Van je site halen. Deze review is in strijd met het reviewbeleid bevonden. |
Kijk dus altijd eerst naar status voordat je de tekst uitleest. Een review die terugkeert naar published komt gewoon opnieuw voorbij, compleet met zijn tekst.
Reviews die nog wachten op de bevestiging van hun schrijver staan er nooit in. Die zijn nooit openbaar geweest.
GET /api/v1/reviews/summary
De cijfers van je bedrijf in één aanroep, zodat je er een keurmerk of een blok op je eigen homepage van kunt maken zonder eerst alles op te halen.
Vraagt om reviews:read.
HTTP 200
{
"ok": true,
"company": { "id": 12, "name": "Voorbeeld Wonen", "slug": "voorbeeld-wonen" },
"stats": {
"reviewCount": 412,
"averageScore": 8.7,
"distribution": { "1": 2, "2": 1, "10": 188 }
},
"aspects": [
{ "key": "levertijd", "averageScore": 9.1, "reviewCount": 212 }
]
}
distribution telt hoeveel reviews er per cijfer van 1 tot en met 10 staan. aspects zijn de losse cijfers per eigenschap waar je winkel naar vraagt, over al je reviews samen.
Deze cijfers zijn dezelfde als die op je bedrijfspagina staan. Zelf optellen uit de lijst levert een ander getal op zodra je er één mist.
Bladeren met een cursor
Er zijn geen bladzijdenummers. Bij een bladzijdenummer verschuift de hele lijst zodra er onderweg een review bij komt, en dan valt er een tussen twee bladzijden door zonder dat iemand het merkt.
In plaats daarvan draagt elk vol antwoord een nextCursor. Stuur die mee in je volgende aanroep en je krijgt precies de rijen die erop volgen. Ontbreekt nextCursor, dan ben je aan het eind van de lijst.
curl "https://klantreviews.online/api/v1/reviews?cursor=MXwxNzU..." \
-H "Authorization: Bearer kro_live_..."
De cursor is voor jou betekenisloos. Hij is ondertekend, hij hoort bij één bedrijf, en je stuurt hem terug zoals je hem kreeg. Ga je er zelf een verzinnen of aanpassen, dan strandt hij.
HTTP 400
{ "ok": false, "reason": "bad_parameter", "parameter": "cursor" }
Dat is met opzet een fout en geen stille terugval naar de eerste bladzijde. Een systeem dat vooraan opnieuw begint zonder het te weten, leest alles nog een keer in en denkt dat het klopt.
Bijwerken sinds je laatste ronde
Bewaar bij elke ronde de hoogste updatedAt die je hebt verwerkt, en geef die de volgende keer mee als updatedSince. Dan krijg je alleen wat er sindsdien nieuw of gewijzigd is.
GET /api/v1/reviews?updatedSince=2026-08-21T09:26:53.589Z
De lijst loopt van oud naar nieuw. Dat is met opzet: wordt er tijdens je ronde een review aangepast, dan schuift hij naar achteren en kom je hem nog een keer tegen. Sla je op id op, dan overschrijf je hem gewoon met de laatste versie. In de omgekeerde volgorde zou diezelfde review naar voren schuiven en zou je hem overslaan.
Een volledige ronde ziet er zo uit:
- Begin zonder
cursor, met deupdatedSincevan je vorige ronde. - Verwerk
reviewsen sla elke rij op onder zijnid. Bij een andere status danpublishedhaal je hem van je site. - Is er een
nextCursor, vraag dan de volgende bladzijde op met diezelfdeupdatedSinceen die cursor erbij. - Is er geen
nextCursormeer, bewaar dan de hoogsteupdatedAtdie je hebt gezien.
Sommige wijzigingen bij ons raken niet wat jij toont. Dan komt er een review voorbij die er voor jou hetzelfde uitziet als de vorige keer, en overschrijf je hem met dezelfde inhoud. Dat is de veilige kant op.
Meldingen naar je eigen server
Wij kunnen een gebeurtenis zoals een nieuwe review ook naar een adres op je eigen server sturen, zodat je er niet op hoeft te wachten tot je volgende ronde.
Je zet dat adres neer in je account, onder Webhooks. Daar kies je waarover je bericht wilt, zie je het geheim waarmee wij ondertekenen, en staat er een knop om een testlevering te sturen voordat er een echte gebeurtenis langskomt.
Webhooks horen bij dezelfde pakketten als de orderingang. Zakt je pakket eronder, dan blijven je adressen staan en gaat er niets meer de deur uit.
Waarover je bericht krijgt
| Gebeurtenis | Wanneer | Wat erin staat |
|---|---|---|
review.published |
Er staat een review online. Ook als moderatie er een terugzet of een bezwaar toewijst. | De hele review. |
review.updated |
De schrijver paste zijn tekst, cijfer of naam aan, of liet zijn naam weghalen. | De hele review, zoals hij nu is. |
review.removed |
Moderatie heeft de review offline gehaald. | Alleen het nummer. |
reply.published |
Je hebt onder een review geantwoord en dat antwoord staat publiek. | De hele review, met je antwoord erbij. |
reply.updated |
Je hebt de tekst van een bestaand antwoord herschreven. | De hele review, met het nieuwe antwoord. |
reply.removed |
Je hebt je antwoord ingetrokken. | De hele review, zonder antwoord erbij. |
De review in data.review draagt dezelfde velden als een rij uit GET /api/v1/reviews met status: "published".
Twee gebeurtenissen kun je niet uitzetten
review.updated en review.removed gaan naar elk adres, ook als je ze niet aanvinkt.
Bericht krijgen over wat erbij komt is een keuze. Bericht krijgen over wat weggaat is dat niet. Zou je alleen nieuwe reviews aannemen, dan blijft je site een review tonen die bij ons is weggehaald omdat hij vals bleek, of een naam die de schrijver juist heeft laten weghalen. Dat is geen koppeling die minder bericht krijgt; dat is een koppeling die zonder het te weten onjuiste dingen publiceert.
Hoe een levering eruitziet
POST /jouw-ontvanger HTTP/1.1
Content-Type: application/json; charset=utf-8
User-Agent: KlantreviewsWebhook/1.0 (+https://klantreviews.online)
Klantreviews-Event: review.published
Klantreviews-Delivery: 6f1c8a92-3f7d-4a1e-9c02-5b8e1d4a77f0
Klantreviews-Signature: t=1772409600,v1=9f2c3b5e1d4a77f0c2b8e1d4a77f0c2b8e1d4a77f0c2b8e1d4a77f0c2b8e1d4a
{
"id": "6f1c8a92-3f7d-4a1e-9c02-5b8e1d4a77f0",
"event": "review.published",
"createdAt": "2026-03-01T12:00:00Z",
"data": {
"company": { "id": 12, "slug": "voorbeeld-wonen", "name": "Voorbeeld Wonen" },
"review": {
"id": 4711,
"authorName": "Marieke de Groot",
"score": 9,
"title": "Netjes geleverd",
"content": "De doos stond er de volgende dag al.",
"language": "nl",
"verification": "verified",
"publishedAt": "2026-03-01T12:00:00Z",
"editedAt": null,
"orderedAt": "2026-02-24T00:00:00Z"
}
}
}
Bij review.removed staat er in data.review alleen { "id": 4711 }. De tekst is op dat moment niet meer openbaar, en je hebt aan het nummer genoeg om hem van je site te halen.
Antwoord met een code in de reeks 200 en doe je verwerking daarna. Een ontvanger die eerst zijn eigen werk afmaakt en dan pas antwoordt, loopt tegen onze tijdslimiet van tien seconden aan.
De handtekening nakijken
Elke levering draagt de kopregel Klantreviews-Signature. Daarin staan twee delen, gescheiden door een komma.
| Deel | Betekenis |
|---|---|
t |
Het moment waarop wij ondertekenden, in hele seconden sinds 1970. |
v1 |
Een HMAC met SHA-256, in hexadecimaal, over de tekst t plus een punt plus het lichaam woordelijk. |
De sleutel van die HMAC is het geheim van dit adres, precies zoals je het bij het aanmaken te zien kreeg.
- Haal
tenv1uit de kopregel. - Bewaar het ruwe lichaam voordat je framework het inleest. Twee JSON-teksten met dezelfde betekenis hebben verschillende bytes, en de handtekening gaat over de bytes.
- Zet de tekst samen: de waarde van
t, een punt, en dan het ruwe lichaam. - Reken de HMAC met SHA-256 uit over die tekst, met je geheim als sleutel.
- Vergelijk het resultaat met
v1in constante tijd. Een gewone stringvergelijking verraadt langs de duur waar hij afbreekt.
import hashlib, hmac, time
def klopt(kop: str, geheim: str, lichaam: bytes, venster: int = 300) -> bool:
delen = dict(stuk.split("=", 1) for stuk in kop.split(","))
stempel, ondertekening = delen.get("t"), delen.get("v1")
if not stempel or not ondertekening:
return False
if abs(time.time() - int(stempel)) > venster:
return False
verwacht = hmac.new(
geheim.encode(),
stempel.encode() + b"." + lichaam,
hashlib.sha256,
).hexdigest()
return hmac.compare_digest(verwacht, ondertekening)
Weeg t altijd mee en houd een venster aan, bijvoorbeeld vijf minuten. Zonder die stap blijft een geldige handtekening eeuwig geldig, en dan kan iemand die één bericht van ons onderschept dat elke dag opnieuw aanbieden; de handtekening klopt immers. Het moment tekent mee, dus het is niet te verzetten zonder de handtekening kapot te maken. Hoe ruim je venster is, is aan jou: alleen jij weet hoe ver de klok van je server afloopt.
Weiger alles wat niet klopt en antwoord dan met een 400. Wij proberen het daarna opnieuw, dus een tijdelijke fout aan jouw kant kost je geen gebeurtenis.
Dubbele leveringen
Elke levering draagt een eigen nummer, in het veld id en in de kopregel Klantreviews-Delivery.
Wij beloven hoogstens één keer próberen per poging en niet hoogstens één keer aankomen. Verdwijnt je antwoord onderweg, dan zien wij een mislukte poging terwijl jij hem allang hebt verwerkt, en dan komt hij nog een keer langs. Dat is aan onze kant niet te zien en dus niet op te lossen.
Bewaar daarom dat nummer en sla een levering over die je al hebt gezien. Dat is het gereedschap waarmee je het aan jouw kant wél oplost.
De volgorde is ook geen belofte. Twee gebeurtenissen die vlak na elkaar ontstaan kunnen elkaar onderweg inhalen. Kijk naar createdAt als de volgorde voor jou uitmaakt, en gebruik de id van de review als de sleutel waarop je opslaat.
Pogingen, wachttijden en opgeven
Antwoord je met iets anders dan een code in de reeks 200, of komt er niets, dan proberen wij het opnieuw.
| Na poging | Wachttijd tot de volgende |
|---|---|
| 1 | 1 minuut |
| 2 | 10 minuten |
| 3 | 1 uur |
| 4 | 4 uur |
| 5 | 18 uur |
Na de zesde poging geven we die levering op. Bij elkaar is dat ongeveer een etmaal, en dat dekt een storing van een middag en een onderhoudsavond.
Een opgegeven levering blijft bij ons staan met de reden erbij, negentig dagen lang. Zo is later terug te zien wát er niet is aangekomen en niet alleen dát er iets misging. Geslaagde leveringen ruimen we na veertien dagen op.
Een adres dat wij nooit kunnen bereiken krijgt geen tweede poging. Dat gaat om een adres dat geen https is, dat op een andere poort staat, dat onleesbaar is, dat binnen een netwerk ligt, of dat ons omleidt. Die vijf veranderen niet door het nog eens te proberen.
Gaan er twintig leveringen op rij verloren, dan zetten wij dat adres stil. Je ziet dat in je account staan, met de reden erbij, en je zet het daar weer aan zodra jouw kant het weer doet. Eén levering die wel aankomt zet de teller terug op nul.
Wat er nooit in staat
Dezelfde grens als bij de API hierboven. Een webhook draagt van een review niet meer dan een bezoeker van je bedrijfspagina ziet:
- Het e-mailadres van de schrijver.
- Het ordernummer waar de review aan hangt.
- De versleutelde herkomst van de schrijver, en elke sleutel waarmee hij zijn review kan bevestigen, aanpassen of aanvechten.
- De motivering van een moderator, en de reden waarom een review is verwijderd.
- De tekst van een review die van je site af hoort. Bij
review.removedkrijg je alleen het nummer. - Een antwoord dat nog een concept is. Pas als het publiek staat gaat het mee.
De testknop
De testlevering uit je account draagt in het veld event de naam test. Die naam hoort bij geen enkele gebeurtenis die is gebeurd en komt nooit uit de gewone wachtrij.
Antwoord er dus wel op en schrijf er verder niets van weg. Zo weet je dat je adres bereikbaar is en dat je handtekeningcontrole klopt, voordat er een echte review langskomt.
Grenzen
De tellers lopen per uur en per sleutel, met een ruimere teller per IP-adres eroverheen.
| Route | Per sleutel | Per adres |
|---|---|---|
POST /orders |
1.000 per uur | 5.000 per uur |
POST /collect-tokens |
5.000 per uur | 20.000 per uur |
GET /reviews en GET /reviews/summary |
2.000 per uur | 10.000 per uur |
Zit je erboven, dan krijg je een Retry-After mee in de kopregels.
HTTP 429
{ "ok": false, "reason": "rate_limited" }
Een nachtelijke ronde over tienduizend reviews kost met limit=200 vijftig aanroepen, dus deze grenzen zitten je bij gewoon gebruik nergens in de weg.
Verder ligt het lichaam van een verzoek op 64 kB, en weigeren wij een veld dat wij niet kennen. Een tikfout in een veldnaam levert dus een 400 op in plaats van een bestelling waarin stilletjes iets ontbreekt.
Wat er nooit in het antwoord staat
Een sleutel ziet van een review niet meer dan een bezoeker van je bedrijfspagina ziet. Deze gegevens komen er dus nooit uit, ook niet voor je eigen bedrijf en ook niet op verzoek:
- Het e-mailadres van de schrijver.
- Het ordernummer waar de review aan hangt.
- De versleutelde herkomst van de schrijver, en elke sleutel waarmee hij zijn review kan bevestigen, aanpassen of aanvechten.
- De tekst van een review die van je site af hoort.
- De vorige versie van een aangepaste review. Dat er is aangepast lees je in
editedAt.
Wil je je reviewgegevens compleet uitdraaien, met de adressen erbij, dan doe je dat in je beheerscherm onder je eigen naam. Daar komt een regel in het logboek onder te staan, en dat hoort ook zo.
Wat /api/v1 belooft
Het versienummer in het pad is een afspraak. Zolang er v1 staat, geldt het volgende.
Velden verdwijnen niet en veranderen niet van betekenis. Wat er vandaag in staat, staat er morgen ook in, met hetzelfde type en dezelfde betekenis. Wie score inleest als geheel getal van 1 tot en met 10, mag dat blijven doen. Let op dat dit per soort rij geldt: id, status en updatedAt staan op elke rij, en de inhoudelijke velden staan op een rij met status: "published".
Velden mogen erbij komen. Sla onbekende velden dus over in plaats van erop te stranden.
Nieuwe waarden binnen een bestaand veld mogen erbij komen. Er kan een taal bij komen in language en een label in verification. Vergelijk je op een vaste lijst, behandel een onbekende waarde dan als iets wat je niet kent en laat je koppeling doorlopen.
Verandert een veld echt van betekenis, dan komt er een nieuw veld naast. Het oude blijft staan met de oude betekenis, tot er een v2 is.
De cursor is voor jou betekenisloos. Wij mogen zijn inhoud vrij veranderen. Wie hem uit elkaar haalt en zelf in elkaar zet, valt buiten deze afspraak.
Foutmeldingen op een rij
Elk antwoord dat misgaat draagt ok: false en een reason die je in code kunt vergelijken.
| Code | reason |
Wat eraan te doen is |
|---|---|---|
| 400 | malformed |
Het lichaam is geen geldige JSON, of er staat een veld in dat wij niet kennen. |
| 400 | bad_parameter |
De parameter in parameter klopt niet. Bij cursor stuur je de waarde terug zoals je hem kreeg; bij updatedSince hoort er een tijdzone in. |
| 401 | unauthorized |
De sleutel ontbreekt, is ingetrokken of hoort bij een bedrijf dat op pauze staat. |
| 403 | missing_scope |
Deze sleutel draagt het recht in scope niet. Zet het aan in je account. |
| 403 | plan_limit |
Je pakket staat de API niet toe. Het pakket dat je nu hebt staat in plan. |
| 404 | unknown |
Er staat geen uitnodiging voor dit ordernummer. |
| 409 | written |
Deze klant heeft zijn review al geschreven. |
| 422 | errors |
Per veld een code, met dezelfde veldnamen als je instuurde. Bijvoorbeeld {"email": "required"}. |
| 429 | rate_limited |
Te veel aanroepen. Wacht de tijd uit Retry-After af. |
| 500 | server |
Er ging bij ons iets mis. Probeer het later opnieuw. |
Kom je er niet uit, stuur ons dan een bericht via contact met het ordernummer of het reviewnummer erbij. Zet je API-sleutel er nooit in.