Hoe het werkt
Webhooks stel je in als actie Stuur webhook in een automatisering in Factora. Je kiest een trigger (bijvoorbeeld *Factuur betaald*), eventuele voorwaarden en de URL die Factora moet aanroepen. Er is geen apart API-endpoint om webhooks te registreren.
- 1Open Automatiseringen in Factora en maak een nieuwe automatisering.
- 2Kies een trigger, bijvoorbeeld
invoice.paid. - 3Voeg de actie Stuur webhook toe met je HTTPS-URL, de methode (
POST,PUTofPATCH) en een geheim (secret). - 4Voeg eventueel eigen headers en eigen velden toe; je kunt daarin variabelen uit de trigger gebruiken.
- 5Test de automatisering met een simulatie en zet hem aan.
Beschikbare triggers
| Groep | Triggers |
|---|---|
| Facturen | invoice.created, invoice.updated, invoice.sent, invoice.viewed, invoice.paid, invoice.partially_paid, invoice.overdue, invoice.due_soon, invoice.cancelled, invoice.payment_failed, invoice.status_changed |
| Offertes | quote.created, quote.updated, quote.sent, quote.viewed, quote.accepted, quote.rejected, quote.expired, quote.due_soon |
| Klanten | customer.created, customer.updated, customer.deleted, customer.label_added, customer.label_removed, customer.status_changed, customer.balance_threshold, customer.inactive |
| Betalingen | payment.received, payment.failed, payment.refunded, payment.partial, payment.provider_succeeded, payment.provider_failed |
| Boekhouding | accounting.document_added, accounting.purchase_invoice_added, accounting.receipt_added, accounting.document_scanned, accounting.document_processed, accounting.transaction_added, accounting.entry_created, accounting.period_closed |
| Abonnement | subscription.started, subscription.changed, subscription.cancelled, subscription.limit_reached, subscription.payment_failed, subscription.expiring |
| Organisatie | user.added, user.removed, user.role_changed, organization.created, organization.settings_changed |
| CRM-module | crm.lead.created, crm.lead.status_changed, crm.lead.converted, crm.lead.unattended, crm.deal.created, crm.deal.stage_changed, crm.deal.won, crm.deal.lost, crm.deal.stale, crm.task.overdue, crm.contact.created |
| Planning | schedule.once, schedule.daily, schedule.weekly, schedule.monthly, schedule.weekday, schedule.month_start, schedule.month_end, schedule.cron |
| Handmatig | manual |
Het verzoek dat je ontvangt
POST /webhooks/factora HTTP/1.1
Content-Type: application/json
X-Factora-Event: invoice.paid
X-Factora-Delivery: 9d4a1f3e-2c5b-4e8a-b7d1-0f6e9c3a2b18
X-Factora-Signature: 5b2c0e4f…(64 hex-tekens){
"event": "invoice.paid",
"automation_id": "9d4a1f3e-1111-4c2d-9e8f-7a6b5c4d3e2f",
"run_id": "9d4a1f3e-2c5b-4e8a-b7d1-0f6e9c3a2b18",
"data": {
"organization": {
"id": "9d3c…10",
"name": "Demo BV",
"email": "info@demo.nl"
},
"customer": {
"id": "9d3c…02",
"name": "Jansen Bouw",
"email": "info@jansenbouw.nl",
"status": "active",
"outstanding_balance": 0
},
"invoice": {
"id": "9d3c…aa",
"number": "2026-0142",
"status": "paid",
"total": "€ 181,50",
"total_cents": 18150,
"amount_due": "€ 0,00",
"amount_due_cents": 0,
"days_overdue": 0
},
"quote": null,
"trigger": []
},
"custom": {
"bron": "factora"
}
}| Veld of header | Betekenis |
|---|---|
event / X-Factora-Event | De trigger die de automatisering startte |
run_id / X-Factora-Delivery | Uniek per uitvoering; gebruik het om dubbele leveringen te herkennen |
automation_id | De automatisering die de webhook stuurde |
data | De variabelen van de trigger: organization, customer, invoice, quote, trigger, date en bij CRM-triggers ook deal-, lead- en contactgegevens. Niet van toepassing = null. Bedragen staan zowel opgemaakt (total) als in centen (total_cents) |
custom | De eigen velden die je in de actie hebt ingesteld |
X-Factora-Signature | HMAC-SHA256 (hex) van de ruwe body met je geheim; alleen als je een geheim instelde |
Handtekening controleren
Bereken HMAC-SHA256 over de ruwe body zoals je hem ontvangt, met het geheim uit de automatisering, en vergelijk die in constante tijd met X-Factora-Signature. Parse de JSON pas daarna.
// Laravel-route die een Factora-webhook ontvangt.
Route::post('/webhooks/factora', function (Request $request) {
$expected = hash_hmac('sha256', $request->getContent(), config('services.factora.webhook_secret'));
abort_unless(hash_equals($expected, (string) $request->header('X-Factora-Signature')), 401);
$event = $request->input('event'); // bijv. "invoice.paid"
$delivery = $request->header('X-Factora-Delivery'); // uniek per uitvoering
// Verwerk idempotent: sla $delivery op en sla dubbele leveringen over.
ProcessFactoraEvent::dispatch($event, $request->input('data'), $delivery);
return response()->noContent();
});Levering en betrouwbaarheid
- Alleen openbare
https://-adressen zijn toegestaan; lokale en privé-adressen worden geweigerd en redirects niet gevolgd. - Factora wacht maximaal 10 seconden op antwoord en probeert het bij een fout na 300 ms nog één keer.
- Een
4xx- of5xx-antwoord of een time-out geldt als mislukt en is zichtbaar in de uitvoeringen van de automatisering. - Antwoord snel met
204en verwerk het werk daarna in een wachtrij. - Verwerk idempotent op basis van
X-Factora-Delivery: dezelfde levering kan vaker binnenkomen. - Wil je zeker zijn van de actuele stand? Haal het object na de webhook op via de API.
Lees ook
- LimietenPer API-sleutel 120 verzoeken per minuut. Zo lees je de limietheaders en ga je om met 429.
- FacturenConceptfacturen maken en bijwerken, versturen, betalingen registreren, annuleren met creditnota en PDF downloaden.
- CodevoorbeeldenKant-en-klare voorbeelden in curl, JavaScript, PHP en Python voor de meest gebruikte koppelingen.