User guide
Webhooks
POST /webhooks/stripe (served by the dashboard) lets Stripe tell InvoiceGuard when a client pays. On a verified checkout.session.completed event, the matching invoice is marked paid automatically.
Setup
- Stripe dashboard → Developers → Webhooks → Add endpoint.
- URL:
https://YOUR-HOST/webhooks/stripe. - Select the event
checkout.session.completed. - Copy the Signing secret (
whsec_...) and configure it (below).
Signing-secret resolution order
The endpoint resolves the secret like this — first hit wins:
INVOICEGUARD_STRIPE_WEBHOOK_SECRETenvironment variablestripe_webhook_secretin~/.invoiceguard/config.yaml- Legacy
STRIPE_WEBHOOK_SECRETenvironment variable (backward compatibility)
Placeholder values (whsec_..., anything ending in ...) count as unconfigured. With no real secret, the endpoint returns HTTP 500 with a message telling you exactly what to set. Bad signatures return HTTP 400.
Events handled
| Stripe event | Result |
|---|---|
checkout.session.completed | invoice marked paid (matched by the invoiceguard_invoice_id metadata on the payment link; invoice_id is a tolerated legacy alias; falls back to matching stripe_session_id) |
payment_link.expired | ignored — nothing is auto-voided in v1; void from the CLI or dashboard |
| anything else | ignored |
Already-paid invoices are left alone (ignored:already_paid); void invoices are likewise ignored — void is terminal, so a webhook never resurrects one; events without invoice metadata are ignored.
Offline smoke test
Signature verification uses real stripe.Webhook.construct_event HMAC checks and works with no network:
INVOICEGUARD_DB=/tmp/ig-demo.db python seed_demo.py --fresh
INVOICEGUARD_DB=/tmp/ig-demo.db INVOICEGUARD_STRIPE_WEBHOOK_SECRET=whsec_test_x \
invoiceguard dashboard
# POST a locally-signed test payload to http://127.0.0.1:8000/webhooks/stripe
The test suite covers this path end to end, including the metadata-key regression (invoiceguard_invoice_id vs invoice_id) and the secret-resolution regression.