Webshop synchronisatie

In dit voorbeeld bouwen we een webhook endpoint dat productwijzigingen van Boom automatisch verwerkt in een webshop. Geen polling, geen cronjobs — je webshop is altijd up-to-date.

Hoe het werkt

┌──────────┐    product.updated      ┌──────────────┐    UPDATE products     ┌──────────┐
│          │ ──────────────────────▶ │   Webhook    │ ─────────────────────▶ │ Webshop  │
│   API    │                         │   Endpoint   │                        │    DB    │
└──────────┘                         └──────────────┘                        └──────────┘
  1. Boom stuurt een webhook wanneer een product wijzigt
  2. Je endpoint verifieert de signature en verwerkt de payload
  3. Je webshop database wordt bijgewerkt

Stap 1: Registreer je webhook

Abonneer je op product events via de API:

Webhook registreren

POST
/v1/webhooks
curl https://api.example.nl/v1/webhooks \
  -H "Authorization: Bearer {token}" \
  -H "Content-Type: application/json" \
  -d '{
    "notificationUrl": "https://mijn-webshop.nl/api/webhooks",
    "events": [
      "product.created",
      "product.updated",
      "product.deleted"
    ]
  }'

Stap 2: Bouw je webhook endpoint

Express (Node.js)

Een compleet voorbeeld met signature verificatie en database synchronisatie:

import express from 'express'
import crypto from 'crypto'
import db from './db.js'

const app = express()
app.use(express.json())

// Verifieer de webhook signature
function verifySignature(req) {
  const signature = req.headers['x-webhook-signature']
  const hash = crypto
    .createHmac('sha256', process.env.WEBHOOK_SECRET)
    .update(JSON.stringify(req.body))
    .digest('hex')
  return hash === signature
}

app.post('/api/webhooks', async (req, res) => {
  // 1. Verifieer signature
  if (!verifySignature(req)) {
    return res.status(401).json({ error: 'Invalid signature' })
  }

  const { type, payload } = req.body

  // 2. Verwerk het event
  switch (type) {
    case 'product.created':
      await db.product.create({
        data: {
          ean: payload.ean,
          name: payload.name,
          price: parseFloat(payload.price),
          available: payload.status === 'available',
          imageUrl: payload.imageUrl,
        },
      })
      break

    case 'product.updated':
      await db.product.update({
        where: { ean: payload.ean },
        data: {
          name: payload.name,
          price: parseFloat(payload.price),
          available: payload.status === 'available',
          imageUrl: payload.imageUrl,
        },
      })
      break

    case 'product.deleted':
      await db.product.delete({
        where: { ean: payload.ean },
      })
      break
  }

  // 3. Bevestig ontvangst
  res.sendStatus(200)
})

app.listen(3000)

ASP.NET Minimal API

using System.Security.Cryptography;
using System.Text;

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

bool VerifySignature(HttpRequest request, string body)
{
    var signature = request.Headers["x-webhook-signature"].ToString();
    var secret = Environment.GetEnvironmentVariable("WEBHOOK_SECRET");
    using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret));
    var hash = Convert.ToHexString(
        hmac.ComputeHash(Encoding.UTF8.GetBytes(body))
    ).ToLower();
    return hash == signature;
}

app.MapPost("/api/webhooks", async (HttpContext context, AppDbContext db) =>
{
    var body = await new StreamReader(context.Request.Body).ReadToEndAsync();

    if (!VerifySignature(context.Request, body))
        return Results.Unauthorized();

    var evt = JsonSerializer.Deserialize<WebhookEvent>(body);

    switch (evt.Type)
    {
        case "product.created":
            db.Products.Add(new Product {
                Ean = evt.Payload.Ean,
                Name = evt.Payload.Name,
                Price = evt.Payload.Price,
                Available = evt.Payload.Status == "available"
            });
            break;

        case "product.updated":
            var product = await db.Products.FindAsync(evt.Payload.Ean);
            if (product != null) {
                product.Name = evt.Payload.Name;
                product.Price = evt.Payload.Price;
                product.Available = evt.Payload.Status == "available";
            }
            break;

        case "product.deleted":
            var toDelete = await db.Products.FindAsync(evt.Payload.Ean);
            if (toDelete != null) db.Products.Remove(toDelete);
            break;
    }

    await db.SaveChangesAsync();
    return Results.Ok();
});

app.Run();

Stap 3: Test je integratie

Gebruik de API om een product aan te maken of bij te werken en verifieer dat je webhook endpoint de notificatie ontvangt:

Test: product bijwerken

# Update een product — dit triggert een product.updated webhook
curl -X PUT https://api.example.nl/v1/products/9789493113862 \
  -H "Authorization: Bearer {token}" \
  -H "Content-Type: application/json" \
  -d '{"price": "34.95"}'

Controleer je webhook endpoint logs om te verifiëren dat de product.updated notificatie is ontvangen en verwerkt.

Best practices

  • Verifieer altijd de signature — accepteer geen requests zonder geldige x-webhook-signature
  • Retourneer snel een 200 — verwerk zware taken asynchroon (queue) om timeouts te voorkomen
  • Maak je handler idempotent — dezelfde notificatie kan meerdere keren worden bezorgd
  • Log ontvangen events — handig voor debugging en auditing
  • Gebruik HTTPS — webhook URLs moeten altijd HTTPS zijn

Volgende stappen

Was this page helpful?