booktohelp
Automation & developers

Integrations & webhooks

The connection is shared across bookto tools; this guide explains checkout's order payloads.

What this does

The moment a customer pays for one of your products, bookto can send the order details to your active webhook or Zapier connection. The API key and webhook settings are shared at account level; this page explains checkout's order events and payload. For the shared setup and other tool events, see Connect bookto to Zapier and other tools.

Two keys, two purposes

On the shared Integrations page in the bookto app, open the API key and webhook settings. They show two different keys. They do opposite things — here's the difference in plain terms.

API key bk_… — incoming

You only need this if you connect through Zapier. You paste this key into Zapier so that Zapier is allowed to sign in to your bookto account. It is shown only once, right after you generate it — after that it can't be read again, so store it somewhere safe. In the API key & webhooks card you can Regenerate the key (replaces it — the old one stops working immediately) or Revoke it (disables it entirely).

Signing secret — outgoing

This is created automatically for you. bookto uses it to "sign" every webhook it sends, so the receiver can confirm the message genuinely came from bookto and wasn't faked by someone else. You only need to look at this if you're building that check into your own server. In the API key & webhooks card, use Reveal to see it while you build your verification, or Regenerate to roll it (any server that verifies signatures will need the new value). If you connect via Zapier or just don't verify the signature, you can ignore it.

Two ways to connect

1. Your own webhook URL

If you have a URL that can receive data (your own server, or a tool like Make or n8n), add it under Webhooks and choose the checkout order events it should receive. No API key is needed for a raw webhook URL.

2. Through Zapier

Generate an API key on the Integrations page and paste it into the bookto app inside Zapier. Zapier then handles the webhook for you. See Send checkout orders to Zapier for the checkout-specific setup.

Verifying the signature (for developers)

If you build your own server to receive webhooks, you can check that each request really came from bookto. Every POST includes these headers:

  • X-Bookto-Event — the event name, e.g. order.paid or order.created
  • X-Bookto-Timestamp — the time the webhook was sent, in unix seconds
  • X-Bookto-Signature — the signature, in the form sha256=<hex>

To verify: compute HMAC-SHA256(signing_secret, "<timestamp>.<raw JSON body>") and compare it to the signature header. They should match exactly. Use the raw, unparsed request body — not a re-stringified version.

import crypto from 'crypto'

// "signingSecret" is the value from your Integrations page.
// "rawBody" is the exact, unparsed request body (a string).
function isFromBookto(req, rawBody, signingSecret) {
  const timestamp = req.headers['x-bookto-timestamp']
  const signature = req.headers['x-bookto-signature'] // "sha256=<hex>"

  const expected =
    'sha256=' +
    crypto
      .createHmac('sha256', signingSecret)
      .update(timestamp + '.' + rawBody)
      .digest('hex')

  return signature === expected
}

Connecting n8n

n8n can receive checkout order webhooks — no extra install or native bookto node is needed. Add a Webhook trigger node in n8n, register its URL in bookto, and verify each delivery with the snippet below. The same account-level connection can also carry events from other tools.

  1. In n8n, add a Webhook trigger node and copy its Production URL. Use the Production URL, not the Test URL — the Test URL doesn't expose the raw body, so signature verification can't work.
  2. On that node, open Options and turn on Raw Body. This is required: without it, the exact bytes bookto signed aren't available and the check will always fail.
  3. In bookto, open Integrations, paste the URL under Webhooks, choose the checkout event, and add it.
  4. Add a Code node (Code in JavaScript) right after the Webhook node. Set its Mode to Run Once for All Items, and paste the snippet below to verify the signature.
  5. Publish the workflow — this activates the Production URL — then place a test order to see it come through.

One quirk worth knowing: with Raw Body on, current n8n (v2.x) delivers the body as binary, not as $json.rawBody. The snippet reads it with the getBinaryDataBuffer helper, which is why the Code node must run once for all items. Verified live against n8n Cloud 2.30.

const crypto = require('crypto');
const secret = 'YOUR_SIGNING_SECRET'; // from bookto Integrations

// "Raw Body" ON delivers the body as binary — read it as a string:
const buffer  = await this.helpers.getBinaryDataBuffer(0, 'data');
const rawBody = buffer.toString('utf8');

const headers   = $input.first().json.headers;
const timestamp = headers['x-bookto-timestamp'];
const received  = headers['x-bookto-signature']; // "sha256=<hex>"

const expected = 'sha256=' + crypto
  .createHmac('sha256', secret)
  .update(`${timestamp}.${rawBody}`, 'utf8')
  .digest('hex');

if (received !== expected) {
  throw new Error('Invalid bookto signature — ignoring.');
}
return [{ json: JSON.parse(rawBody) }];

Replace YOUR_SIGNING_SECRET with the real value from bookto Integrations — store it in an n8n credential, never hard-code it in a shared workflow.

What's in the payload

Each checkout order event uses this JSON shape. Other bookto tools send their own event payloads. Here's an example of a checkout order:

{
  "event": "order.paid",
  "payment_provider": "mollie",
  "order_id": "…",
  "order_number": "ORD-…",
  "status": "paid",
  "created_at": "…",
  "paid_at": "…",
  "amount": {
    "total_incl": 1.21,
    "excl_vat": 1,
    "vat": 0.21,
    "vat_rate": 21,
    "currency": "EUR"
  },
  "vat_treatment": "standard",
  "product": { "name": "…", "description": "…" },
  "buyer": {
    "first_name": "…",
    "last_name": "…",
    "email": "…",
    "company": "…",
    "vat_number": "…",
    "peppol_id": "…",
    "address": "…",
    "address2": "…",
    "postal_code": "…",
    "city": "…",
    "country": "BE",
    "match_ref": "…"
  },
  "custom_fields": [ { "label": "…", "value": "…" } ],
  "seller": { "id": "…", "entity": null },
  "is_test_order": false
}

The checkout payload carries the order information an invoicing or bookkeeping tool needs: buyer details, the product description, VAT figures and any checkout custom_fields. It is plain JSON, so Zapier, Make, n8n or your own endpoint can read it.

One customer, not a duplicate every time

When a returning buyer orders again, you want your accounting tool to reuse their existing contact, not create a second one. bookto does not merge contacts for you (we never want to accidentally merge two different people) — instead every payload includes a stable key, buyer.match_ref, that your scenario uses to find-or-create the contact. It is the buyer's VAT number when present, otherwise their peppol id, otherwise their email — normalised so it stays the same across orders.

The pattern (Zapier or Make)

  1. Trigger — "New paid order" (Zapier) or a custom webhook (Make / n8n), pointed at the URL from your Webhooks list.
  2. Search — "Find Customer/Contact" in your accounting app, searching on buyer.match_ref (or buyer.vat_number / buyer.email if your app searches on those).
  3. Create if not found — most apps have a "Find or create" step; otherwise add a "Create Customer" step that only runs when the search came back empty. Map name, email, address, vat_number and peppol_id.
  4. Create the invoice/order — use the matched (or newly created) customer, the product name + description, the amount figures, and the vat_treatment. Add custom_fields as a note if your buyers fill them in.

Tip: match on the VAT number first — it is unique to a business, so it is the safest key for B2B. Falling back to email is fine for private buyers, but two different people could in theory share a mailbox, so prefer VAT where you have it.

Testing safely

A payment made in test mode also sends a webhook, marked with "is_test_order": true. That lets you try the whole flow without taking a real payment. A handy trick: paste a webhook.site URL into your Webhooks list to see exactly what bookto sends.

No duplicate notifications

Each paid order is delivered to your webhook only once, even if the payment provider repeats its own notification behind the scenes. You won't get the same order twice.

Still stuck?

Contact support →