Skip to content
Rechnungskit

Securing checkout webhooks: API key and signature in Make, Zapier and n8n

Updated

You secure every checkout webhook from Rechnungskit with an API key of your choice, sent as X-Api-Key and also as x-make-apikey. If you want, an HMAC-SHA256 signature in the header X-Rechnungskit-Signature comes on top. In Make this switches on the built-in API key authentication, in n8n the Header Auth of the webhook node, and in Zapier you compare the header with a filter behind a Catch Raw Hook.

With the Rechnungskit checkout you sell through a link, and the invoice is created automatically. What happens after the purchase is up to a webhook: unlock access, generate a license, trigger shipping, process further in Make, Zapier or n8n. This page shows how to set up your webhook receiver so it accepts only genuine messages from Rechnungskit.

Delivery · at least once
From the purchase to the accepted message
Event
checkout.paid / checkout.failedone-time purchase succeeded or failed
subscription.*seven subscription events
Rechnungskit sends
  1. POST with a JSON bodyto your URL
  2. Headersevent ID, plus API key and signature if set up
Your receiver
2xxdelivered
other status or no answernew attempt after 15 s, then twice as long, at most 1 hour, up to six attempts in total
On retries the same event ID comes again. Use it to detect duplicates.

What Rechnungskit sends

For every checkout outcome, Rechnungskit sends a POST request with a JSON body to your URL: as checkout.paid on success and as checkout.failed on failure. The message carries these headers (signature and API key only if you set them up when creating the webhook):

Header Content
X-Rechnungskit-Signature sha256=<hex>, the HMAC-SHA256 signature of the body, if switched on
X-Rechnungskit-Event-Id Unique event ID for duplicate detection (also in the body)
X-Api-Key Your optional API key, if set when creating the webhook
x-make-apikey The same API key again, in the header name Make checks natively

You choose which events an endpoint receives when you create it (and any time later) with the event checklist on the webhook. The test button on the webhook checks the whole route per event with a sample event, without triggering a real purchase.

Delivery is at least once. If your receiver does not answer with a 2xx status, Rechnungskit tries again, up to six times in total, with growing intervals.

Subscription events (subscription.*)

If you also sell subscriptions through the checkout, Rechnungskit reports every step in the subscription lifecycle as its own event. That way your system can switch access automatically:

Event When it fires Your typical reaction
subscription.activated The subscription is active (first payment confirmed or trial started) Unlock access
subscription.charged A charge succeeded (start, renewal or upgrade, see charge.kind) Extend the term, file the document
subscription.payment_failed A charge failed; charge.attempt/charge.maxAttempts/charge.nextAttemptAt show the retry ladder Do NOT lock yet, only at the end of the ladder
subscription.updated Plan or quantity changed; change carries before/after Adjust seats/features
subscription.canceled A cancellation came in, effective at the end of the period Make a note, don't lock anything yet
subscription.ended The subscription is really over (endedReason: period_end, immediate or dunning) Lock access now
subscription.withdrawn Consumer withdrawal through the withdrawal button (§ 356a BGB) Lock access, the refund is under way
Subscription lifecycle
When to revoke access
  1. subscription.activatedunlock access
  2. subscription.payment_faileddon't block yetonly at the end of the ladder
  3. subscription.canceledmake a notetakes effect at period end
  4. subscription.endedrevoke access nowsame for subscription.withdrawn

The subscription.* messages use their own versioned envelope ("version": 1). Besides event, eventId and occurredAt, subscription always holds the complete current state: status, product, plan, quantity, amount, period, buyer, your metadata and booked add-ons as an addons list. change (on changes) and charge (on charges) come as separate blocks.

You recognize test purchases by the test field: for checkout.* it sits at the end of the payload, for subscription.* at the top level of the envelope. "test": true means the purchase ran in test mode or through your test credentials, and no real money moved. For real purchases it says false.

Rely on the absolute state, not on the order of the messages. Delivery is at least once and without an ordering guarantee. Detect duplicates by eventId, and for subscription.updated take the state with the latest occurredAt.

The security is identical to the checkout events: same endpoint, same API key, same signature. One endpoint can subscribe to checkout and subscription events at the same time.

Ad attribution in the payload (click IDs and UTM)

If a buyer lands on the checkout with ad parameters (fbclid, gclid, msclkid, ttclid or utm_source to utm_term) and agrees in the consent bar, Rechnungskit freezes them on the purchase. They then appear in every webhook message:

Where Field
checkout.* additional field attribution at the end of the payload
subscription.* attribution inside subscription
Redirect to the thank-you page (return URL on the product) the same parameters plus rk_order, rk_event_id, rk_amount_cents, rk_currency

The field is null if the buyer came without ad parameters, rejected, or did not decide at all. Without consent, the return URL only gets the rk_ parameters, no click IDs and no UTM parameters.

When the bar appears

The bar only appears when a tracking integration is active in the project (Meta Ads, Google tag or TikTok pixel) and a privacy policy URL is set. Without it the field is always null, because click IDs and UTM parameters are only stored with consent (§ 25 Abs. 1 TDDDG, EDPB Guidelines 2/2023, para. 50 f.).

Rechnungskit checks the stored consent at the time of sending. Orders from before the switch (October 2026) have no record and come without attribution.

What you can use the data for

This lets you report conversions to ad platforms server-side, for example to the Meta Conversions API with fbclid to attribute the campaign. Or you analyze your channels yourself.

The parameters on the return URL are there so your own conversion tag can fire on your thank-you page. rk_event_id is the event ID the native Meta integration also uses, so Meta can deduplicate against your own browser pixel.

The existing field order before the new field stays unchanged, so existing signature checks keep working.

Two safeguards: API key and signature

Your webhook URL is already the first safeguard: it contains a long random part and is practically impossible to guess. If you want more, there are two levels:

  1. API key (simple): when creating the webhook in Rechnungskit, you enter a key of your choice. It is sent as a header with every delivery. Your receiver only compares the value, no cryptography involved. This is the right way for Make, Zapier and n8n.
  2. HMAC signature (strong): If you switch on "Also send an HMAC signature" when creating the webhook, Rechnungskit signs the exact JSON body with a signing secret (whsec_…, shown exactly once after saving). Recomputing the signature proves that the message comes from Rechnungskit and was not altered. This is the right way for your own code and for receivers with crypto support.
Safeguards compared
Three levels, one is usually enough
Unguessable URL
A long random part in the address. Always there, nothing to set up
Standardthe usual safeguard in Zapier
API key
Headers X-Api-Key and x-make-apikey. The receiver only compares the value
ForMake, n8n, Zapier
HMAC signature
Header X-Rechnungskit-Signature over the exact body, with the secret whsec_…
Foryour own code
What gets sent is what you set up when creating the webhook. Which of it you check is up to your receiver.

The three tools at a glance

Tool Where you check the key Header Without the matching key
Make Custom webhook, API Key authentication x-make-apikey HTTP 401
n8n Webhook node, Authentication Header Auth X-Api-Key HTTP 403
Zapier Catch Raw Hook plus Filter by Zapier X_Api_Key (Zapier uses underscores) the Zap stops

Make: API key authentication

Make checks API keys natively on the custom webhook and expects the key in the header x-make-apikey. That is exactly where Rechnungskit sends your key automatically, so no renaming or extra logic is needed.

  1. In your scenario, add the module Webhooks, Custom webhook (or open your existing webhook) and open the webhook's advanced settings.
  2. Under API Key authentication, click Add API key and create a new keychain. Enter exactly the key you set as the API key when creating the webhook in Rechnungskit. For security reasons, Make does not show the value again afterwards.
  3. Save. From now on, Make answers every message without the matching key with HTTP 401, and your scenario only starts for genuine Rechnungskit events.

Two useful extras in the same webhook settings:

  • IP restrictions: a comma-separated list of allowed sender IPs.
  • Get request headers: if you want to reuse header values in the scenario.

You can also recompute the HMAC signature in Make: the function sha256 accepts a key parameter and then returns an HMAC. That needs JSON pass-through and header capture switched on, though, and a filter that compares the values. For Make we recommend the API key.

n8n: Header Auth

The n8n webhook node has header checking built in as its own authentication type:

  1. Open your Webhook node and set Authentication to Header Auth.
  2. Under Credential for Header Auth, create a new credential of type Header Auth. As Name, enter X-Api-Key, as Value the API key you set in Rechnungskit. The case of the header name does not matter.
  3. Save and activate the workflow. n8n rejects messages without the matching key with HTTP 403.

If you want to check the signature instead, switch on the option Raw Body on the webhook node so the exact body is preserved. Then either the Crypto node (Action Hmac, Type SHA256, Encoding HEX, Secret = your whsec_…) or a Code node with require('crypto') computes the signature. An IF node then compares "sha256=" + hmac with the header x-rechnungskit-signature.

Zapier: Catch Raw Hook and filter

Zapier has no built-in key check on the webhook trigger; the unguessable catch URL is the standard safeguard there. If you also want to check the API key, you need the trigger type with headers:

  1. As the trigger, use Webhooks by Zapier with the event Catch Raw Hook. Not Catch Hook: the normal Catch Hook only parses the body and returns no headers. Catch Raw Hook requires a paid Zapier plan.
  2. Fire a test event ("Send test event" in Rechnungskit) so Zapier learns the structure. The headers appear with underscores, so your key is in the field headers X_Api_Key.
  3. Right after the trigger, add a Filter by Zapier: Only continue if, field X_Api_Key, condition (Text) Exactly matches, value = your API key.

You can check the signature in Zapier too. The Catch Raw Hook delivers the unchanged body in the field raw. A Code by Zapier step (Python with hmac/hashlib or JavaScript with crypto) computes the signature from it and outputs a comparison result for the filter to check. For most Zaps, the API key filter is the practical way.

Checking the signature yourself (HMAC-SHA256)

For your own endpoints (your server, a cloud function, the n8n code node), you recompute the signature over the unchanged request body, with the signing secret from the webhook setup:

const crypto = require('crypto');

function verify(rawBody, signatureHeader, secret) {
	const expected =
		'sha256=' + crypto.createHmac('sha256', secret).update(rawBody, 'utf8').digest('hex');
	return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signatureHeader));
}

Three pitfalls from practice:

  1. Raw body: you really have to hash the raw body, not re-serialized JSON. Even a different field order changes the signature.
  2. Safe comparison: timingSafeEqual is better than a plain ===.
  3. Duplicates: detecting them by event ID pays off. Rechnungskit delivers at least once, and on retries the same X-Rechnungskit-Event-Id comes again.

Frequently asked questions

Do I need the API key and the signature at the same time?

No. The API key is the simple way for no-code tools and fully sufficient for most cases, especially since your webhook URL cannot be guessed anyway. The signature is the cryptographically stronger proof that the message really comes from Rechnungskit and was not altered on the way.

What gets sent is what you set up when creating the webhook: the API key if you entered one, and the signature if "Also send an HMAC signature" is on. Which of them you check is up to your receiver.

Where do I set the API key in Rechnungskit?

When creating the webhook under Connections → API, in the section Checkout webhooks, field "API key: your credential for n8n / Make (optional)". The key is stored encrypted and not shown again afterwards. To change it, you create the webhook again.

Why do I see the signing secret only once?

The secret is treated like a password: it is stored encrypted and never handed out again after creation. Copy it into your target system right after saving. If it gets lost, simply create the webhook again and you get a new secret.

What happens if my receiver rejects the webhook?

Rechnungskit tries to deliver every message up to six times with growing intervals: starting at 15 seconds, doubling up to a maximum of one hour. If your receiver answers with a 2xx status, the message counts as delivered.

Every attempt is logged on the server with status code and error text. For persistent delivery problems, support can help you with the log.

Which events and data does the webhook contain?

For one-time purchases there are checkout.paid and checkout.failed. The JSON body contains, among other things, the product, amount and currency, the buyer's email and name, the delivery country, the invoice number (once created) and your passed-through metadata.

For subscriptions there are seven events of their own (subscription.activated to subscription.withdrawn) with a versioned envelope that always carries the complete subscription state. A unique event ID is in the body and in the header X-Rechnungskit-Event-Id, so you can detect duplicate deliveries.

Sources

Where to find it in the app

Recent changes

Rechnungskit is not a tax advisory or law firm. This article explains general principles and does not replace advice from a tax advisor (Steuerberater, § 5 StBerG) or a lawyer (§ 3 RDG). Rechnungskit is built for businesses based in Germany and prepares documents, tax rates and bookings automatically. How your specific case is treated remains your decision, ideally together with your tax advisor or a lawyer.

de en