Guides
Webhook sync met CRM
Houd je CRM automatisch gesynchroniseerd met OptimoCMS via webhooks. Leer hoe je webhooks instelt, payloads verwerkt en signatures verifieert.
Webhook sync met je CRM
Webhooks sturen real-time notificaties naar jouw server wanneer er iets gebeurt in OptimoCMS — een formulier wordt ingestuurd, een bestelling geplaatst, of een pagina gepubliceerd.
In deze tutorial:
- Webhook aanmaken via de SDK
- Express server om payloads te ontvangen
- Cryptografische signature verificatie
- Payload verwerken en doorsturen naar een CRM
Vereisten
- Node.js 18+
npm install @optimocms/sdk express- Een publiek bereikbaar endpoint (gebruik ngrok voor lokaal testen)
Stap 1 — Webhook aanmaken
SDK
import { OptimoCMS } from '@optimocms/sdk';
const client = new OptimoCMS({
apiKey: process.env.OPTIMOCMS_API_KEY!,
});
const webhook = await client.webhooks.create('site_abc123', {
url: 'https://jouw-server.nl/webhooks/optimocms',
events: ['form.submitted', 'order.created', 'booking.created'],
});
console.log(`Webhook aangemaakt: ${webhook.id}`);
console.log(`Secret: ${webhook.secret}`);
// Bewaar dit secret veilig — je hebt het nodig voor signature verificatieREST
curl -X POST https://api.optimocms.com/v1/sites/$SITE_ID/webhooks \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://jouw-server.nl/webhooks/optimocms",
"events": ["form.submitted", "order.created", "booking.created"]
}'Response:
{
"id": "wh_abc123",
"url": "https://jouw-server.nl/webhooks/optimocms",
"events": ["form.submitted", "order.created", "booking.created"],
"secret": "whsec_k7x9m2p4q8r1t5w3",
"active": true,
"createdAt": "2026-05-26T14:00:00Z",
"failCount": 0,
"lastDeliveredAt": null
}Stap 2 — Webhook endpoint bouwen
Maak een Express server die webhook payloads ontvangt en verwerkt:
import express from 'express';
import crypto from 'node:crypto';
const app = express();
const WEBHOOK_SECRET = process.env.OPTIMOCMS_WEBHOOK_SECRET!;
// Raw body nodig voor signature verificatie
app.post(
'/webhooks/optimocms',
express.raw({ type: 'application/json' }),
(req, res) => {
// Stap 1: Signature verificatie
const signature = req.headers['x-optimocms-signature'] as string;
const timestamp = req.headers['x-optimocms-timestamp'] as string;
if (!verifySignature(req.body, signature, timestamp)) {
console.error('Ongeldige webhook signature');
return res.status(401).json({ error: 'Invalid signature' });
}
// Stap 2: Parse payload
const event = JSON.parse(req.body.toString());
// Stap 3: Verwerk op basis van event type
switch (event.type) {
case 'form.submitted':
handleFormSubmission(event.data);
break;
case 'order.created':
handleNewOrder(event.data);
break;
case 'booking.created':
handleNewBooking(event.data);
break;
default:
console.log(`Onbekend event type: ${event.type}`);
}
// Stap 4: Bevestig ontvangst (200 OK)
res.status(200).json({ received: true });
}
);
app.listen(3000, () => {
console.log('Webhook server draait op port 3000');
});Stap 3 — Signature verificatie
OptimoCMS ondertekent elke webhook met HMAC-SHA256. Verifieer dit altijd vóór verwerking:
function verifySignature(
body: Buffer,
signature: string,
timestamp: string
): boolean {
if (!signature || !timestamp) return false;
// Voorkom replay attacks: weiger payloads ouder dan 5 minuten
const timestampMs = parseInt(timestamp, 10);
const ageMs = Date.now() - timestampMs;
if (ageMs > 5 * 60 * 1000) {
console.error(`Webhook te oud: ${ageMs}ms`);
return false;
}
// Bereken verwachte signature
const payload = `${timestamp}.${body.toString()}`;
const expected = crypto
.createHmac('sha256', WEBHOOK_SECRET)
.update(payload)
.digest('hex');
// Timing-safe vergelijking voorkomt timing attacks
const expectedBuffer = Buffer.from(expected, 'hex');
const receivedBuffer = Buffer.from(signature, 'hex');
if (expectedBuffer.length !== receivedBuffer.length) return false;
return crypto.timingSafeEqual(expectedBuffer, receivedBuffer);
}Stap 4 — Payload verwerken naar CRM
Voorbeeld: formulierdata doorsturen naar HubSpot CRM:
interface FormSubmission {
formId: string;
siteId: string;
fields: Record<string, string>;
submittedAt: string;
}
async function handleFormSubmission(data: FormSubmission): Promise<void> {
const { fields } = data;
// Maak een contact in HubSpot
const response = await fetch('https://api.hubapi.com/crm/v3/objects/contacts', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.HUBSPOT_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
properties: {
firstname: fields.firstName || fields.name?.split(' ')[0] || '',
lastname: fields.lastName || fields.name?.split(' ').slice(1).join(' ') || '',
email: fields.email,
phone: fields.phone || '',
company: fields.company || '',
hs_lead_status: 'NEW',
source: `OptimoCMS form: ${data.formId}`,
},
}),
});
if (!response.ok) {
const error = await response.text();
console.error(`HubSpot sync mislukt: ${error}`);
throw new Error(`CRM sync failed: ${response.status}`);
}
console.log(`✓ Contact aangemaakt in HubSpot voor ${fields.email}`);
}
async function handleNewOrder(data: { orderId: string; customer: { email: string; name: string }; totalCents: number }) {
console.log(`Nieuwe bestelling: ${data.orderId} van ${data.customer.name} (€${(data.totalCents / 100).toFixed(2)})`);
// Sync naar je CRM, ERP of boekhoudsoftware
}
async function handleNewBooking(data: { bookingId: string; customer: { name: string; email: string }; date: string; startTime: string }) {
console.log(`Nieuwe reservering: ${data.bookingId} - ${data.customer.name} op ${data.date} om ${data.startTime}`);
// Voeg toe aan Google Calendar, stuur bevestiging, etc.
}Stap 5 — Webhook deliveries monitoren
Controleer of webhooks succesvol afgeleverd worden:
SDK
// Alle deliveries ophalen
for await (const delivery of client.webhooks.listDeliveries('site_abc123', 'wh_abc123')) {
const status = delivery.success ? '✓' : '✗';
console.log(`${status} ${delivery.event} → ${delivery.statusCode} (${delivery.durationMs}ms)`);
}
// Mislukte events opnieuw afspelen
const replay = await client.webhooks.replay('site_abc123', 'wh_abc123', {
since: '2026-05-25T00:00:00Z',
eventTypes: ['form.submitted'],
});
console.log(`${replay.replayed} events opnieuw verstuurd`);REST
# Deliveries bekijken
curl https://api.optimocms.com/v1/sites/$SITE_ID/webhooks/$WEBHOOK_ID/deliveries \
-H "Authorization: Bearer $API_KEY"
# Replay mislukte events
curl -X POST https://api.optimocms.com/v1/sites/$SITE_ID/webhooks/$WEBHOOK_ID/replay \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{ "since": "2026-05-25T00:00:00Z" }'Beschikbare webhook events
| Event | Trigger |
|---|---|
form.submitted | Bezoeker stuurt een formulier in |
order.created | Nieuwe webshop bestelling |
order.updated | Bestelling status wijzigt |
booking.created | Nieuwe reservering |
booking.cancelled | Reservering geannuleerd |
page.created | Nieuwe pagina aangemaakt |
page.updated | Pagina-inhoud gewijzigd |
page.deleted | Pagina verwijderd |
site.published | Site gepubliceerd naar productie |
Webhook payload structuur
Elke webhook delivery bevat:
{
"id": "evt_abc123",
"type": "form.submitted",
"siteId": "site_xyz789",
"createdAt": "2026-05-26T15:30:00Z",
"data": {
"formId": "contact-form",
"fields": {
"name": "Jan de Vries",
"email": "jan@voorbeeld.nl",
"message": "Ik wil graag een tafel reserveren voor 4 personen."
},
"submittedAt": "2026-05-26T15:30:00Z"
}
}Best practices
- Altijd signature verifiëren — Vertrouw nooit blindelings op inkomende requests
- Respond snel met 200 — Verwerk de payload asynchroon als het lang duurt
- Idempotency — Gebruik het
idveld om duplicaten te detecteren - Retry-tolerant — OptimoCMS herhaalt mislukte deliveries automatisch (max 5x)
- Logging — Log elke inkomende webhook voor debugging
Volgende stappen
- API Reference: Webhooks — Volledige webhook API documentatie
- CI/CD pipeline — Automatiseer deployments
- Restaurantsite bouwen — Voorbeeld-site met formulieren