PlennoHelp

Webhooks instellen en beveiligen

Statuswijzigingen van aanvragen automatisch doorsturen naar je eigen systeem, met HMAC-ondertekening.

Alleen eigenaren/beheerders

Doel

Wanneer een aanvraag die via de Leads-API is binnengekomen van status verandert, kan Plenno dat automatisch doorsturen naar een eigen systeem via een webhook-URL.

Vooraf nodig

  • Je bent eigenaar, of teamlid met het lidtype Ontwikkelaar, en Ontwikkelaarsfuncties staat aan (zie De Leads-API in het kort).
  • Een HTTPS-eindpunt op je eigen server dat POST-verzoeken kan ontvangen.

Nog geen beheerscherm. Een webhook-eindpunt instellen kan op dit moment nog niet via een schermpje in Plenno — dat komt nog. Tot die tijd richt je een eindpunt in door, terwijl je in Plenno bent ingelogd, zelf een verzoek te doen naar de eindpunten-API hieronder (bijvoorbeeld via de ontwikkelaarsconsole van je browser, of een klein script dat met je sessie meestuurt). Dit is dezelfde beveiligde, sessiegebonden API die het toekomstige beheerscherm ook zal gebruiken.

Een eindpunt registreren

GET  /api/organization-settings/webhooks
POST /api/organization-settings/webhooks

Deze twee routes zijn niet de publieke Leads-API: ze werken met je eigen, ingelogde Plenno-sessie (dezelfde cookie als de rest van de app), niet met een pln_live_/pln_test_ API-sleutel, en zijn alleen bereikbaar vanaf app.plenno.nl zelf.

POST body:

{
  "url": "https://jouw-systeem.example.com/plenno/webhook",
  "subscribed_events": ["lead_status_changed", "lead_won", "lead_lost"]
}
  • url moet HTTPS zijn, zonder gebruikersnaam/wachtwoord in de URL, maximaal 2048 tekens.
  • subscribed_events is een niet-lege lijst uit de events hieronder.
  • Het antwoord (HTTP 201) bevat het secret voor de HMAC-ondertekening — exact één keer. Plenno bewaart het daarna alleen versleuteld en kan het niet opnieuw tonen; raak je het kwijt, registreer dan een nieuw eindpunt.
  • Maximaal 10 actieve eindpunten per organisatie.

Een eindpunt weer uitschakelen kan met:

DELETE /api/organization-settings/webhooks/{id}

Dit is onomkeerbaar via deze route (geen "heractiveren") — een nieuw eindpunt aanmaken is de weg terug.

Beschikbare events

Event Wanneer
lead_status_changed De pijplijnfase van de aanvraag is gewijzigd.
lead_won De aanvraag is als gewonnen verkoopkans afgesloten.
lead_lost De aanvraag is als verloren verkoopkans afgesloten.

Alleen gebeurtenissen van aanvragen die via de publieke Leads-API zijn binnengekomen, worden op dit moment doorgestuurd. Testaanvragen (zie Live- en testsleutels) veroorzaken geen webhookgebeurtenis.

De payload

{
  "event_id": "3fbe9e2a-0e1e-4b6a-9b7e-2b7b5a5b9b10",
  "event_type": "lead_status_changed",
  "occurred_at": "2026-09-23T09:14:02.000Z",
  "lead_id": "7543de5d-bc88-4ea8-afe3-2696856157e5",
  "external_id": "website-form-123456",
  "status": "gesprek gepland",
  "attribution": {
    "utm_source": "google",
    "utm_medium": "cpc",
    "gclid": "Cj0KCQjw..."
  }
}
  • external_id is de referentie die je zelf meegaf bij het aanmaken (null als je die niet gebruikte).
  • attribution bevat alleen de zes toegestane sleutels (utm_source, utm_medium, utm_campaign, utm_content, utm_term, gclid) die je destijds in metadata meestuurde — nooit de rest van je vrije metadata. Ontbrekende sleutels worden weggelaten.
  • status is "won"/"lost" bij die events, en anders de naam van de pijplijnfase.

Headers en HMAC-ondertekening

Elk verzoek is POST, Content-Type: application/json, met deze headers:

Header Betekenis
x-plenno-webhook-signature HMAC-SHA256 over "${timestamp}.${rawBody}", als hex-string.
x-plenno-webhook-timestamp Tijdstip van verzending, in milliseconden sinds epoch, als string.
x-plenno-webhook-event-id Hetzelfde event_id als in de payload — handig om dubbele ontvangst te herkennen.

De handtekening is HMAC-SHA256(secret, "${timestamp}.${rawBody}"), waarbij rawBody exact de bytes zijn die zijn verstuurd — reken de handtekening dus na over de ruwe, ontvangen body, niet over een opnieuw geserialiseerd object (sleutelvolgorde en spaties kunnen dan verschillen).

Voorbeeld 8 — een webhook ontvangen en verifiëren

// Node.js, Express — pas aan naar je eigen framework.
import crypto from "node:crypto";

const WEBHOOK_SECRET = process.env.PLENNO_WEBHOOK_SECRET;

app.post("/plenno/webhook", express.raw({ type: "application/json" }), (req, res) => {
  const timestamp = req.header("x-plenno-webhook-timestamp");
  const signature = req.header("x-plenno-webhook-signature");
  const rawBody = req.body; // Buffer — nog niet geparsed als JSON.

  if (!timestamp || !signature) return res.status(400).end();

  const expected = crypto
    .createHmac("sha256", WEBHOOK_SECRET)
    .update(`${timestamp}.${rawBody.toString("utf8")}`, "utf8")
    .digest("hex");

  const providedBuffer = Buffer.from(signature, "utf8");
  const expectedBuffer = Buffer.from(expected, "utf8");
  const isValid =
    providedBuffer.length === expectedBuffer.length &&
    crypto.timingSafeEqual(providedBuffer, expectedBuffer);

  // Optioneel, aanbevolen: wijs een te oud tijdstip af (bv. > 5 minuten), als
  // bescherming tegen een replay van een oud, ooit geldig verzoek.
  const ageMs = Date.now() - Number(timestamp);
  if (!isValid || Number.isNaN(ageMs) || Math.abs(ageMs) > 5 * 60_000) {
    return res.status(401).end();
  }

  const payload = JSON.parse(rawBody.toString("utf8"));
  // ... verwerk payload.event_type, payload.lead_id, enzovoort.

  res.status(200).end();
});

Belangrijk: lees de body als ruwe bytes (express.raw, niet express.json) vóórdat je hem parseert — de handtekening rekent over exact die bytes.

Retrygedrag en foutafhandeling

  • Plenno wacht maximaal 8 seconden op een antwoord.
  • Alleen een HTTP-statuscode 200–299 telt als geslaagd.
  • Bij een mislukte poging volgt een nieuwe poging met kwadratisch oplopende wachttijd (5 minuten × pogingnummer², met een plafond van 60 minuten), tot maximaal 8 pogingen; daarna telt de aflevering als definitief mislukt.
  • Een eindpunt dat 20 pogingen achter elkaar mislukt, wordt automatisch gepauzeerd. Een nieuw eindpunt aanmaken is dan de weg terug.
  • Plenno volgt nooit een HTTP-redirect (3xx) op een webhookaanroep; dat telt als mislukt.

Beveiligingseisen

  • Het eindpunt moet HTTPS zijn.
  • Verzoeken naar interne, privé- of link-local-adressen (zoals localhost, 127.0.0.1, 192.168.x.x, of een cloud-metadata-adres) worden geweigerd, zowel bij het registreren als bij elke aflevering.
  • Verifieer altijd x-plenno-webhook-signature vóórdat je de payload verwerkt. Verwerk nooit een verzoek met een ontbrekende of ongeldige handtekening.

Beperkingen

  • Er is nog geen beheerscherm voor webhook-eindpunten — zie hierboven.
  • Testwebhooks worden nog niet ondersteund. Een testaanvraag (testsleutel) verandert nooit van status via een echte pijplijn-actie op een manier die een webhook triggert op dit moment.
  • Alleen de drie genoemde events zijn beschikbaar; er zijn geen andere events of configuratieopties.

Vervolg

Lees De Leads-API in het kort voor het overzicht, of Live- en testsleutels om een koppeling eerst veilig uit te proberen.

Laatst bijgewerkt: 2026-09-23 · Gebaseerd op Plenno v6.57.0