OptimoCMSDocs
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 verificatie

REST

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

EventTrigger
form.submittedBezoeker stuurt een formulier in
order.createdNieuwe webshop bestelling
order.updatedBestelling status wijzigt
booking.createdNieuwe reservering
booking.cancelledReservering geannuleerd
page.createdNieuwe pagina aangemaakt
page.updatedPagina-inhoud gewijzigd
page.deletedPagina verwijderd
site.publishedSite 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

  1. Altijd signature verifiëren — Vertrouw nooit blindelings op inkomende requests
  2. Respond snel met 200 — Verwerk de payload asynchroon als het lang duurt
  3. Idempotency — Gebruik het id veld om duplicaten te detecteren
  4. Retry-tolerant — OptimoCMS herhaalt mislukte deliveries automatisch (max 5x)
  5. Logging — Log elke inkomende webhook voor debugging

Volgende stappen

On this page