btcpaymailer/WEBHOOK.md
Erling c2b5d23477 Validate BTCPay webhooks with BTCPAY-SIG HMAC-SHA256
Replace URL token auth with the official webhook secret signature check.
Add SETUP.md and separate TEST_TOKEN for the Postmark test endpoint.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 12:20:00 +02:00

74 lines
1.7 KiB
Markdown

# BTCPay webhook reference
## Endpoint
```
POST https://mailer.nxtgroup.org/btcpay-webhook
```
No query-string token. BTCPay signs each request with the **webhook secret** from the store webhook settings.
## Authentication
BTCPay sends header `BTCPAY-SIG`:
```
sha256=<hmac-sha256-hex>
```
The HMAC is computed over the **raw request body bytes** using the webhook **Secret** shown in BTCPay (same value as `BTCPAY_WEBHOOK_SECRET` in Portainer).
## Disable quickly
1. **BTCPay** → Store → **Webhooks** → delete or disable the webhook.
2. **Portainer** → Stacks → `btcpaymailer` → stop/remove the stack.
## Event subscribed
Only **`InvoiceReceivedPayment`** is processed. Other events return `200` with `"ignored"` (silent unless `DEBUG=true`).
## Webhook body (BTCPay → mailer)
```json
{
"deliveryId": "abc123",
"webhookId": "wh_xyz",
"isRedelivery": false,
"type": "InvoiceReceivedPayment",
"timestamp": 1717843200,
"storeId": "your-store-id",
"invoiceId": "invoice-id-here"
}
```
The app then fetches the full invoice:
```
GET {BTCPAY_URL}/api/v1/stores/{storeId}/invoices/{invoiceId}
```
## Send conditions
1. Valid `BTCPAY-SIG` signature
2. `type == InvoiceReceivedPayment`
3. BTCPay API returns invoice
4. `status == "New"`
5. `0 < amountPaid < amount`
6. `metadata.buyerEmail` is set
## Response codes
| Code | Meaning |
|------|---------|
| `401` | Missing or invalid `BTCPAY-SIG` |
| `400` | Partial payment but no `buyerEmail` |
| `500` | BTCPay API or Postmark failure |
| `200` | Ignored, or email sent |
## Logging
| `DEBUG` | Container logs |
|---------|----------------|
| `false` | Evaluation + send result for payment events only |
| `true` | Full payload and every decision step |