Ga naar de hoofdinhoud
Printproducten vanaf 1 stuk

Webhooks: orderupdates automatisch ontvangen

11 min leestijdBijgewerkt

Met webhooks stuurt Printen.eu je een bericht zodra er iets met een order gebeurt: wacht op saldo, in productie, verzonden met tracking, afgeleverd of geannuleerd. Zo hoef je de API niet steeds te bevragen. In dit artikel voeg je een endpoint toe, controleer je de handtekening en bouw je een verwerking die tegen herhalingen kan.

In dit artikel

Hoe werken webhooks?

Je geeft ons een https-url van je eigen server. Bij elke gebeurtenis sturen we daar een POST-verzoek naartoe met een JSON-bericht. Je server antwoordt met een 2xx-status, en dan weten wij dat het bericht is aangekomen.

Van gebeurtenis tot verwerking.
  • X-Printen-Event: het soort event, bijvoorbeeld order.shipped.
  • X-Printen-Delivery: een id van deze aflevering. Handig in je logs.
  • X-Printen-Timestamp: het tijdstip van verzenden, in seconden sinds 1970 (Unix-tijd).
  • X-Printen-Signature: de handtekening (v1= gevolgd door een hex-code), waarmee je controleert dat het bericht echt van ons komt.

Hoe voeg je een endpoint toe?

Het tabblad Webhooks in je dashboard.
  1. Open het tabblad

    Ga in je dashboard naar Webhooks.

  2. Vul je url in

    Vul bij Url (https) het adres van je eigen server in, bijvoorbeeld https://jouwdomein.nl/printen-webhook.

  3. Kies de events

    Vink aan welke events je wilt ontvangen. Standaard staan order.shipped, order.payment_failed en order.awaiting_funds aangevinkt.

  4. Voeg de webhook toe

    Klik op Webhook toevoegen. Je ziet nu het geheim, met de melding Bewaar dit geheim nu. Je ziet het maar één keer.

  5. Bewaar het geheim

    Zet het geheim direct in de configuratie van je server, bijvoorbeeld als omgevingsvariabele. Je hebt het nodig om de handtekening te controleren.

Je kunt een endpoint ook via de API toevoegen met POST /webhooks. Laat je events weg of stuur je een lege lijst, dan ontvang je alle events. Het geheim staat alleen in dit ene antwoord.

Endpoint toevoegen via de API
curl -X POST https://www.printen.eu/api/reseller/v1/webhooks \
  -H "Authorization: Bearer prk_live_..." -H "Content-Type: application/json" \
  -d '{"url":"https://example.com/printen-webhook","events":["order.shipped","order.delivered"]}'
Antwoord
{
  "data": {
    "id": "WEBHOOK_ID",
    "url": "https://example.com/printen-webhook",
    "events": ["order.shipped", "order.delivered"],
    "allEvents": false,
    "active": true,
    "createdAt": "2026-10-10T09:00:00.000Z",
    "updatedAt": "2026-10-10T09:00:00.000Z",
    "secret": "WEBHOOK_SECRET"
  }
}
  • De url moet https gebruiken en publiek bereikbaar zijn. Adressen als localhost, interne netwerkadressen en namen die eindigen op .local, .internal of .lan worden geweigerd.
  • Je kunt maximaal 10 endpoints per account hebben.
  • Elk endpoint heeft een eigen geheim.

Welke events zijn er?

EventWanneerWat je meestal doet
order.createdEen order is aangemaakt, via de API, het dashboard of een gekoppelde winkel.Ons ordernummer opslaan bij je eigen order.
order.submittedDe order is betaald en gaat naar productie.De order in je systeem op "in behandeling" zetten.
order.awaiting_fundsJe saldo is te laag; de order wacht.Jezelf waarschuwen en opwaarderen.
order.payment_failedDe betaling met je opgeslagen kaart is mislukt.Je kaart controleren en de order opnieuw proberen in het dashboard.
order.in_productionDe productie is begonnen.De status tonen. Annuleren kan vanaf nu niet meer.
order.shippedHet pakket is verzonden; tracking bevat code en link.De tracking naar je klant sturen of in je winkel zetten.
order.deliveredHet pakket is afgeleverd.De order afsluiten.
order.cancelledDe order is geannuleerd.De order in je systeem annuleren en een eventuele terugbetaling verwerken.
order.file_problemEr is een probleem met het drukbestand. We nemen contact met je op.Je bestand controleren en op ons bericht reageren.

Hoe ziet een bericht eruit?

Elk bericht heeft dezelfde opbouw. Hieronder een voorbeeld van order.shipped.

Voorbeeld: order.shipped
{
  "id": "EVENT_ID",
  "type": "order.shipped",
  "created": "2026-10-10T14:32:05.000Z",
  "data": {
    "order": {
      "id": "ORDER_ID",
      "orderNumber": "RS-3F9A1C7B2E",
      "externalOrderId": "1001",
      "externalOrderNumber": "1001",
      "state": "SUBMITTED",
      "status": "SHIPPED",
      "tracking": {
        "code": "3SABCD1234567",
        "url": "https://example.com/track/3SABCD1234567"
      },
      "items": [
        {
          "productId": "PRODUCT_ID",
          "name": "Poster",
          "quantity": 1,
          "options": { "size": "A2" }
        }
      ]
    }
  }
}
VeldBetekenis
idUnieke id van het event. Behandel het als een tekst zonder vaste opbouw en gebruik het om dubbele berichten te herkennen.
typeHet soort event, gelijk aan de header X-Printen-Event.
createdWanneer het event is ontstaan (ISO 8601).
data.order.orderNumberOns ordernummer, zoals je het in het dashboard ziet.
data.order.externalOrderIdJouw eigen order-id, als je die bij het bestellen hebt meegegeven.
data.order.state en statusDe status aan jouw kant (bijvoorbeeld SUBMITTED) en de productiestatus (bijvoorbeeld SHIPPED). Zie orderstatussen.
data.order.trackingTrackingcode en trackinglink, zodra de order verzonden is.
Extra velden in dataBij order.awaiting_funds en order.payment_failed staat de reden in reason. Bij order.cancelled kan refundedCents staan. In de sandbox heeft order.submitted het veld test.

Hoe controleer je de handtekening?

Iedereen kan een verzoek naar je url sturen. Controleer daarom bij elk bericht de handtekening voordat je er iets mee doet. De handtekening is een HMAC-SHA256 met het geheim van je endpoint.

  1. Lees de headers

    Haal X-Printen-Timestamp en X-Printen-Signature uit het verzoek.

  2. Controleer de tijd

    Wijs het bericht af als de timestamp meer dan 5 minuten (300 seconden) afwijkt van de huidige tijd. Zo kan niemand een oud bericht opnieuw afspelen.

  3. Bereken de verwachte handtekening

    Bereken een HMAC-SHA256 met je geheim over de tekst timestamp.rawBody: de timestamp, een punt en daarna de ruwe body. Zet er v1= voor.

  4. Vergelijk veilig

    Vergelijk de uitkomst met X-Printen-Signature met een vergelijking die altijd even lang duurt (constant time). Komen ze niet overeen, antwoord dan met 401 en verwerk niets.

Node.js
import crypto from 'node:crypto';

export function verifyPrintenWebhook(rawBody, headers, secret) {
  const timestamp = headers['x-printen-timestamp'];
  const signature = headers['x-printen-signature'] || '';
  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;
  const expected = 'v1=' + crypto.createHmac('sha256', secret)
    .update(`${timestamp}.${rawBody}`).digest('hex');
  return signature.length === expected.length
    && crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
}
PHP
<?php
function verify_printen_webhook(string $rawBody, string $timestamp, string $signature, string $secret): bool {
    if (abs(time() - (int) $timestamp) > 300) return false;
    $expected = 'v1=' . hash_hmac('sha256', $timestamp . '.' . $rawBody, $secret);
    return hash_equals($expected, $signature);
}
Python
import hashlib, hmac, time

def verify_printen_webhook(raw_body: bytes, timestamp: str, signature: str, secret: str) -> bool:
    if abs(time.time() - int(timestamp)) > 300:
        return False
    expected = "v1=" + hmac.new(secret.encode(), timestamp.encode() + b"." + raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature)

Valkuil: controleer op de ruwe body

De handtekening is berekend over de body precies zoals wij hem versturen, byte voor byte. Veel frameworks lezen de body automatisch als JSON. Zet je die JSON daarna weer om naar tekst, dan kunnen spaties, volgorde of tekens net anders zijn, en klopt de handtekening niet meer.

Express: de ruwe body bewaren
import express from 'express';
import { verifyPrintenWebhook } from './verify-printen-webhook.js';

const app = express();

// express.raw (not express.json) keeps the body exactly as it was sent
app.post('/printen-webhook', express.raw({ type: 'application/json' }), async (req, res) => {
  const rawBody = req.body.toString('utf8');
  if (!verifyPrintenWebhook(rawBody, req.headers, process.env.PRINTEN_WEBHOOK_SECRET)) {
    return res.status(401).send('invalid signature');
  }
  const event = JSON.parse(rawBody);
  await saveEventForProcessing(event); // store first, process later
  res.status(200).send('ok');
});
  • PHP: lees de body met file_get_contents("php://input"), niet met $_POST.
  • Python (Flask): gebruik request.get_data() voor de ruwe bytes, niet request.json.
  • Next.js (App Router): gebruik await request.text() en pas daarna JSON.parse.

Wat als je endpoint niet antwoordt?

Een aflevering is geslaagd als je binnen 10 seconden antwoordt met een 2xx-status. Elke andere uitkomst telt als mislukt: een time-out, een foutstatus, maar ook een doorverwijzing (3xx). Dan proberen we het opnieuw volgens dit schema.

PogingWachttijd na de vorige pogingOngeveer na de eerste poging
1DirectStart
21 minuut1 minuut
35 minuten6 minuten
415 minuten21 minuten
530 minuten51 minuten
61 uur2 uur
72 uur4 uur
84 uur8 uur
98 uur16 uur
108 uur24 uur

Na de tiende poging geven we het voor dat event op. Je endpoint wordt nooit automatisch uitgeschakeld: nieuwe events blijven we gewoon sturen.

Dubbele en ongeordende events verwerken

Door herhalingen kan hetzelfde event meer dan één keer binnenkomen, en events kunnen in een andere volgorde aankomen dan ze zijn ontstaan. Bouw je verwerking daarop.

  • Ontdubbel op `id`: bewaar de id van elk verwerkt event. Komt dezelfde id nog eens binnen, antwoord dan met 2xx en doe verder niets.
  • Let op de volgorde met `created`: heb je al een nieuwer event voor deze order verwerkt, overschrijf dan geen nieuwere status met een oudere.
  • Zet geen status terug: komt order.shipped binnen na order.delivered, laat de order dan op afgeleverd staan.
  • Twijfel je, vraag de order op: met GET /orders/{id} krijg je altijd de actuele stand.

Hoe test je je webhook?

  1. Zet je endpoint klaar

    Zorg dat je url publiek bereikbaar is via https. Werk je lokaal, gebruik dan een tunnelservice die een publieke https-url doorstuurt naar je eigen computer.

  2. Voeg het endpoint toe

    Voeg het toe bij Webhooks en vink order.created, order.submitted en order.cancelled aan (of laat de events weg via de API om alles te ontvangen).

  3. Plaats een sandboxorder

    Plaats met een testsleutel (prk_test_) een order via de API. Binnen een paar minuten ontvang je order.created en order.submitted (met test in data).

  4. Annuleer de testorder

    Annuleer de order met POST /orders/{id}/cancel. Je ontvangt dan order.cancelled.

  5. Controleer je logs

    Kijk of de handtekening klopt, of je 2xx teruggaf en of een herhaald bericht met dezelfde id netjes werd genegeerd.

Problemen oplossen

Ik ontvang helemaal niets.

Controleer of de url klopt, https gebruikt en vanaf internet bereikbaar is (geen localhost of intern adres). Kijk of je de juiste events hebt aangevinkt. Houd er rekening mee dat een event pas na een paar minuten kan aankomen.

De handtekening klopt nooit.

Controleer of je het geheim van dit endpoint gebruikt (elk endpoint heeft een eigen geheim) en of je op de ruwe body controleert, niet op een opnieuw opgebouwde JSON. Zie de ruwe body.

Ik krijg berichten dubbel.

Dat is normaal na een time-out of foutstatus. Ontdubbel op de id van het event. Zie dubbele events.

Mijn server stuurt een doorverwijzing.

We volgen geen doorverwijzingen; een 301 of 302 telt als mislukt. Gebruik de uiteindelijke url, let daarbij op www, een slash aan het eind en http naar https.

Ik heb het geheim niet bewaard.

Verwijder het endpoint en voeg het opnieuw toe. Je krijgt dan een nieuw geheim; pas dat aan in je serverconfiguratie.

Ik kan geen endpoint meer toevoegen.

Je zit op het maximum van 10 endpoints. Verwijder er eerst een die je niet meer gebruikt.

Kom je er niet uit?

We helpen je graag verder, op werkdagen persoonlijk en meestal dezelfde dag.

Alle artikelen