Voor ontwikkelaars
Events en webhooks
Het verschil tussen interne domeinevents, ondertekende uitgaande berichten en betaalcallbacks van providers.
Op deze pagina
Drie verschillende berichtgrenzen
| Mechanisme | Richting | Doel |
|---|---|---|
| PHP-domeinevent | Binnen de applicatie | Gedrag tussen Core en Modules coördineren |
| Uitgaande webhook | Van Agovena naar je HTTPS-endpoint | Een externe integratie informeren |
| Betaalwebhook | Van provider naar Agovena | Providerstatus verifiëren en verwerken |
Deze mechanismen verschillen in payload, authenticatie en herhalingsgedrag. Gebruik het ondertekeningsalgoritme voor uitgaande Agovena-webhooks niet voor een inkomende providercallback, tenzij die provider dat algoritme daadwerkelijk voorschrijft.
Interne domeinevents
Modules registreren listeners via ModuleContext::listen(). Inventory luistert bijvoorbeeld naar OrderPlacing, OrderCreated en OrderCancelled voor voorraadcontrole, reservering en terugboeking.
Het uitvoermoment hoort bij het contract:
- OrderPreflight bevat geprijsde regels en een wijzigbare array
checks.PlaceOrderverstuurt dit event vóór de besteltransactie. - OrderPlacing bevat regels, een optionele bestelling en optionele preflightcontext. Het wordt tijdens de opslag verstuurd, zodat een listener de bestelling vóór commit kan weigeren.
- OrderCreated implementeert
ShouldDispatchAfterCommit. De aanroep staat binnenPlaceOrder, maar het eventcontract stelt de dispatch uit tot na commit. Reken er niet op dat een after-commitlistener een al vastgelegde bestelling kan terugdraaien.
Leg gevolgen van terugbetalingen en annuleringen expliciet vast. Een refundbericht betekent niet automatisch dat een abonnement, service, download of toegangsbewijs moet worden ingetrokken. De Module die het toegangsrecht beheert bepaalt dat beleid.
Houd transactiegevoelige validatie lokaal en voorspelbaar. Plan externe neveneffecten pas nadat de gegevens zijn vastgelegd. Test het gedrag van listeners en niet alleen of een event is verstuurd. Bronnen: PlaceOrder, ModuleContext en AvailabilityCapability.
Catalogus van uitgaande events
De publieke webhookcatalogus bevat momenteel:
| Eventtype | Velden onder data |
|---|---|
order.created |
order_id, order_number, status, payment_status, total, currency |
order.paid |
Dezelfde bestelvelden |
order.cancelled |
Dezelfde bestelvelden |
payment.recorded |
payment_id, order_id, status, amount, currency |
refund.recorded |
refund_id, order_id, amount, currency, status |
credit_note.issued |
credit_note_id, invoice_id, order_id, amount, currency |
ID's verwijzen naar records, bedragen gebruiken de kleinste munteenheid en statussen zijn domeinenumwaarden. Bij bestellingen kan payment_status null zijn. Let op: bestelevents gebruiken total, terwijl de andere payloads amount gebruiken. Geen van beide is de volledige API-resource.
Niet ieder PHP-event wordt automatisch een webhook. De namen staan expliciet in WebhookEventCatalog. WebhookEventSubscriber selecteert de payloadvelden.
HTTP-omhulling en headers
Een gewone HTTP-bestemming ontvangt onderstaande structuur. Alle waarden zijn voorbeelden:
{
"id": "00000000-0000-4000-8000-000000000001",
"type": "order.created",
"created_at": "2026-01-01T12:00:00+00:00",
"data": {
"order_id": 42,
"order_number": "EXAMPLE-42",
"status": "pending",
"payment_status": "pending",
"total": 1200,
"currency": "EUR"
}
}
| Header | Betekenis |
|---|---|
Content-Type |
application/json |
X-Agovena-Event |
Eventtype |
X-Agovena-Delivery |
UUID van de levering |
X-Agovena-Timestamp |
Unix-tijdstip in seconden voor deze poging |
X-Agovena-Signature |
v1= gevolgd door een hexadecimale HMAC-SHA256 |
Payload-ID en leverings-UUID zijn afzonderlijke waarden. De publisher maakt beide identifiers per overeenkomend endpoint, niet één globaal event-ID voor alle bestemmingen. Actieve endpoints abonneren zich op expliciete eventnamen of *. De publisher filtert gevoelige gegevens en zet de levering na commit in de wachtrij.
Discord-bestemmingen krijgen een andere body: een embeds-array op basis van eventtype en data. De handtekening dekt altijd de daadwerkelijk verzonden body, inclusief de bestemmingsopmaak. Bronnen: WebhookEventPublisher en WebhookPayloadFormatter.
Eerst verifiëren, dan verwerken
Het ondertekende bericht bestaat uit het decimale tijdstip, een letterlijke punt en de ruwe HTTP-body. De geheime sleutel is die van het ingestelde endpoint:
v1=HMAC-SHA256(secret, timestamp + "." + raw_body)
Parseer en herschrijf JSON niet vóór verificatie. Witruimte en escaping beïnvloeden de handtekening. Gebruik een vergelijking in constante tijd en weiger oude tijdstippen. WebhookSigner gebruikt standaard een tolerantie van 300 seconden en accepteert handtekeningen gescheiden door komma's of puntkomma's.
Een zelfstandige PHP-verificatiefunctie volgens dit contract:
<?php
function verifyAgovenaWebhook(
string $secret,
string $timestampHeader,
string $rawBody,
string $signatureHeader,
int $now,
): bool {
if ($secret === '' || ! ctype_digit($timestampHeader)) {
return false;
}
$timestamp = (int) $timestampHeader;
if (abs($now - $timestamp) > 300) {
return false;
}
$expected = 'v1='.hash_hmac('sha256', $timestamp.'.'.$rawBody, $secret);
foreach (preg_split('/[;,]/', $signatureHeader) ?: [] as $candidate) {
if (hash_equals($expected, trim($candidate))) {
return true;
}
}
return false;
}
Geef de tijd van je eigen server mee, niet een door de client aangeleverde now. Valideer na verificatie de structuur, bewaar de leveringsidentifier met een unieke constraint en accepteer het werk duurzaam voordat je een 2xx-response geeft. Herhaalde levering mag geen dubbele fulfilment of boeking veroorzaken. Bewaar ondertekeningsgeheimen in een veilige opslag en sluit ruwe gevoelige payloads uit van logs.
Aflevering en herhaling
DeliverWebhook claimt een levering met een lease vóór verzending. De verbindingstime-out is vijf seconden, de totale time-out tien seconden. Een HTTPS-bestemming moet de controle op publieke adressen doorstaan. Redirects zijn uitgeschakeld en het gevalideerde IP wordt voor het verzoek vastgezet.
- Iedere geslaagde 2xx-response markeert de levering als afgeleverd.
- Transportfouten, HTTP 429 en serverfouten kunnen worden herhaald.
- Andere mislukte responses, inclusief redirects en clientfouten anders dan 429, worden niet herhaald.
- Er zijn maximaal vijf pogingen, met wachttijden van 60, 300, 1800 en 7200 seconden.
- Uitgeputte of niet-herhaalbare leveringen krijgen
dead_letter. - Uitgeschakelde bestemmingen worden overgeslagen. De job bewaart geen responsebody.
Bij een volgende poging worden tijdstip en handtekening opnieuw gemaakt. Ontdubbel daarom op leveringsidentiteit, niet op de handtekening. De scheduler draait agovena:deliver-webhooks iedere minuut om wachtend werk op te pakken. Een actieve scheduler en queueworker zijn operationele vereisten, geen optionele clientfuncties.
Inkomende betaalcallbacks
Core registreert POST /webhooks/payments/{gateway} buiten /api/v1. Deze route is uitgesloten van CSRF-controle, omdat providers geen browsersessie hebben. Authenticatie gebeurt via verifyWebhook() van de gateway, niet met een klanttoken.
HandlePaymentWebhook weigert onbekende gateways en gateways zonder webhooks, verifieert vóór parsing, normaliseert de payload, bewaart de identiteit voor herhalingen en verwerkt de betaalstatus met de eigen levenscycluscontroles. Een geslaagde response van PaymentWebhookController bevat ok, het opgeslagen event-ID id en duplicate. Verificatiefouten geven 403; onverwachte verwerkingsfouten geven 500.
Er bestaat geen providerneutrale curl-body waarmee je geldig een betaling kunt bevestigen. Iedere adapter moet het ondertekenings- en payloadprotocol van zijn provider implementeren en testen. Maak geen fictief betaald-voorbeeld dat verificatie omzeilt.
Test de foutpaden
php artisan test tests/Unit/OutboundWebhookContractTest.php
php artisan test tests/Feature/OutboundWebhookDeliveryTest.php
php artisan test tests/Feature/WebhookDeadLetterTest.php
php artisan test tests/Feature/PaymentGatewayWebhookTest.php
OutboundWebhookContractTest test geldige, verlopen en ongeldige handtekeningen en geweigerde SSRF-bestemmingen. Voeg ontvangertests toe voor gewijzigde bodies, dubbele levering, duurzame acceptatie vóór bevestiging en herhaling na een tijdelijke storing.