PrompTom
Alle Skills

Receiving webhooks without double charges

webhook-handlerFREEwebhookspayments

Signature, retries and idempotency — the four rules that stop one payment extending access twice.

Was er tut

  • Triggers on “add a callback endpoint”, “integrate payments”, “it was processed twice”.
  • Starts from the fact that the sender will deliver the same event more than once, out of order, and sometimes much later.
  • Requires verifying the signature over raw bytes rather than reparsed and reserialised JSON: reserialising changes key order, and the signature stops matching for a reason nobody finds quickly.
  • Stores the provider's event id under a unique constraint and does the check and the work in one transaction — two statements race, and under retries they will.
  • Answers 200 as early as possible and works afterwards: work before the response eats the sender's timeout, and a timeout is another retry.
  • Requires 200 even for event types you ignore: a 4xx makes the sender retry forever and eventually disable the endpoint.
  • On a failed signature check, logs the event id and nothing else — not the body, not the header, not the computed digest.

Wozu er gut ist

A provider sends a notification on every status change and retries until it sees success. Without a key on the event id, one payment extends a subscription twice and nobody notices — until accounting does, a month later. Verifying the signature against reserialised JSON, and working before answering, are the two other mistakes almost everyone makes.

Wohin damit

  1. 1Legen Sie im Projekt den Ordner .claude/skills/webhook-handler an
  2. 2Legen Sie dort eine SKILL.md mit dem Text unten ab
  3. 3Fertig. Claude Code lädt den Skill selbst, sobald eine Aufgabe zur Beschreibung passt

Damit der Skill in allen Projekten statt nur in einem funktioniert, legen Sie ihn in ~/.claude/skills statt in den Projektordner.

SKILL.md-Datei

---
name: webhook-handler
description: Receive webhooks from a payment provider or external service correctly. Use when the user adds a callback endpoint, integrates payments, or reports that something was processed twice.
---

# Receiving a webhook

The sender will deliver the same event more than once, out of order, and
sometimes months late. It will retry until you answer 200. Everything
below follows from that.

## The four rules

**1. Verify the signature before reading the body.**
Compute it over the raw bytes, not over the parsed and re-serialised
JSON — reserialising changes key order and whitespace, and the signature
stops matching for reasons nobody finds quickly. Compare in constant
time.

An unsigned endpoint that grants access is a free access endpoint for
anyone who reads your docs.

**2. Be idempotent.**
Store the provider's event id, unique-constrained, and check it before
acting. Do the check and the work in one transaction — two separate
statements race, and under retries they will race, because retries
arrive in bursts.

This is what stops one payment extending a subscription twice.

**3. Answer fast, work after.**
Return 200 as soon as the event is stored. Work done before the
response counts against the sender's timeout, and a timeout means a
retry, which means the same work again.

**4. Answer 200 for events you ignore.**
A 4xx to an event type you do not handle makes the sender retry it
forever and eventually disable the endpoint.

## What to log and what never to log

Log the event id, the type and the outcome. On a failed signature check,
log the event id and nothing else — not the body, not the header, not
the computed digest. Those end up in a log aggregator that more people
can read than you think.

Never log the secret. Never accept it from a query parameter.

## Test it

- Send the same event twice and check the effect happened once.
- Send it with a wrong signature and check nothing happened.
- Send an unknown event type and check the answer is 200.
- Send an event for an object that does not exist locally and check the
  handler does not crash the endpoint for every subsequent event.

## Before finishing

State plainly what happens if the endpoint is down for an hour. If the
answer is "those events are lost", say it — most providers retry for
days, but only if you answered with an error rather than a 200.
Receiving webhooks without double charges — PrompTom