Zet een adres op mijn site waar de kassa na elke verkoop een bericht naartoe
stuurt, en controleer of dat bericht echt van onze kassa komt.
Een webhook van MplusKASSA is een bericht dat de kassa naar jouw server stuurt, bijvoorbeeld na elke afgerekende bon. Zo'n ontvanger draait prima op gewone webhosting met PHP. Het belangrijkste werk zit in één controle: komt dit bericht echt van jouw kassa? Dit artikel loopt de stappen door, met code die we zelf gebruiken op onze testkassa.
Waarom je een webhook liever niet-blokkerend zet, lees je in een webhook die je kassa kan blokkeren.
1. Een adres op je server
Je hebt een HTTPS-adres nodig dat POST-verzoeken aanneemt, bijvoorbeeld https://jouwdomein.nl/hook. Mplus plakt de naam van de gebeurtenis erachter: een afgerekende bon komt binnen op /hook/completeSession. Je ontvanger moet dus alles onder /hook/ afhandelen, of per gebeurtenis een eigen pad hebben.
Zorg dat het certificaat geldig is en blijft. Een webhook naar een adres dat niet antwoordt, is bij een blokkerende instelling een kassa die niet kan afrekenen.
2. De webhook in de backoffice
In de backoffice van Mplus ga je naar Beheer > Instellingen > Webhooks. Daar vul je in:
- het adres uit stap 1;
- een geheime sleutel (base64), die je ook op je server bewaart;
- de gebeurtenissen die je wil ontvangen, bijvoorbeeld
completeSession; - of de webhook blokkerend is. Begin met niet-blokkerend.
Via de API van Mplus kun je de ingestelde webhooks uitlezen, maar niet aanmaken of wijzigen. Instellen gaat dus altijd via de backoffice.
3. De handtekening controleren
Elke aanroep heeft de kop X-Mplus-Signature. Dat is een HMAC-SHA256 over de ruwe inhoud van het bericht, met als sleutel jouw geheime sleutel na base64-decodering, en het resultaat weer in base64.
In PHP:
<?php
$sleutel = base64_decode(getenv('MPLUS_WEBHOOK_SECRET'));
$inhoud = file_get_contents('php://input');
$verwacht = base64_encode(hash_hmac('sha256', $inhoud, $sleutel, true));
$gekregen = $_SERVER['HTTP_X_MPLUS_SIGNATURE'] ?? '';
$klopt = $gekregen !== '' && hash_equals($verwacht, $gekregen);
In Python:
import base64, hashlib, hmac
def handtekening(inhoud: bytes, geheim_b64: str) -> str:
sleutel = base64.b64decode(geheim_b64)
return base64.b64encode(hmac.new(sleutel, inhoud, hashlib.sha256).digest()).decode()
klopt = hmac.compare_digest(gekregen, handtekening(ruwe_inhoud, GEHEIM))
Drie dingen die vaak misgaan:
- De sleutel niet decoderen. De sleutel is base64. Gebruik je de tekst zoals hij in de backoffice staat, dan klopt de handtekening nooit.
- Over de JSON rekenen in plaats van over de ruwe inhoud. Lees je het bericht eerst in en zet je het daarna weer om naar tekst, dan verandert de volgorde of de opmaak en klopt de handtekening niet meer. Reken altijd over de bytes zoals ze binnenkwamen.
- Gewoon
==gebruiken. Vergelijk methash_equalsofcompare_digest, zodat de vergelijking niets prijsgeeft over hoe ver een vervalste handtekening klopte.
4. Antwoorden, en je antwoord ondertekenen
Antwoord met status 200 en JSON. Bij de meeste gebeurtenissen is een leeg object {} genoeg. Je antwoord onderteken je op dezelfde manier, met de kop X-Mplus-Signature:
<?php
$antwoord = '{}';
header('Content-Type: application/json');
header('X-Mplus-Signature: ' . base64_encode(hash_hmac('sha256', $antwoord, $sleutel, true)));
echo $antwoord;
Bij completeSession mag een blokkerende webhook ook een tekst voor onderaan de bon teruggeven, in het veld receiptFooter. Bij een niet-blokkerende webhook wacht de kassa niet op je antwoord en doet zo'n tekst niets.
5. Opslaan, dan pas verwerken
Sla het bericht op, antwoord direct, en verwerk het daarna. Zo blijft je antwoordtijd kort, ook als het verwerken (voorraad bijwerken in je boekhouding, een melding sturen) even duurt.
Bij een foute of ontbrekende handtekening verwerk je het bericht niet. Antwoorden doe je wel, zodat de kassa er geen last van heeft. Bewaar zulke berichten apart, dan zie je het als iemand je adres probeert te misbruiken.
Testen
Reken op de kassa een bon af en kijk of het bericht binnenkomt. Bij ons kwam het binnen een seconde. Controleer in je logboek of de handtekening klopte en of de regels erin staan die je verwacht. Let op velden die ontbreken: een artikel zonder eigen artikelcode of barcode heeft die velden niet in het bericht.
Veelgemaakte fouten
Blokkerend beginnen. Zet een nieuwe webhook altijd eerst op niet-blokkerend. Pas als alles maanden stabiel draait en je antwoord aan de kassa iets toevoegt, kun je blokkerend overwegen.
De sleutel in je code zetten. Bewaar hem in een omgevingsvariabele of een bestand buiten je webmap, niet in de code die in je repository staat.
Elk pad accepteren. Neem alleen de gebeurtenissen aan die je hebt aangezet, en weiger de rest met een korte 404.
Wat je met een gekoppelde kassa kunt, staat op MplusKASSA koppelen.