Skip to content

fix(book): CoinPay webhook signature + nested payload so cookbook sales settle - #136

Merged
ralyodio merged 1 commit into
mainfrom
fix/coinpay-webhook-nested
Oct 21, 2026
Merged

ralyodio merged 1 commit into
mainfrom
fix/coinpay-webhook-nested

Conversation

@ralyodio

Copy link
Copy Markdown
Contributor

CoinPay webhooks never settled a cookbook sale:

  • verifyWebhook compared HMAC(rawBody) against the header; CoinPay sends X-CoinPay-Signature: t=<unix>,v1=<hex> with HMAC-SHA256 over ${t}.${rawBody} (coinpayportal src/lib/webhooks/service.ts). Now parsed, 300s tolerance, timing-safe.
  • Payment id was read from data.id; CoinPay sends data.payment_id and the top-level id is the event. New readWebhook reads nested first, flat fallback.
  • Settles on confirmed/forwarded; markPaid is idempotent (keeps first paid_at) and an id we never sold answers 200 no-op instead of minting a download.
  • Payment metadata adds property: 'hqtui.com' (account_id/email when a caller has them; buyer is anonymous today).
  • Test: apps/web/test/coinpay-webhook.test.ts signs a real-shaped payload.

bun test apps/web/test: 24 pass.

🤖 Generated with Claude Code

…yload

CoinPay signs HMAC-SHA256 of "${t}.${rawBody}" and sends t=<unix>,v1=<hex>
in X-CoinPay-Signature; the route compared a bare-body HMAC and refused every
real webhook. The payment is nested under data (top-level id is the event),
so read data.payment_id/status/metadata first. markPaid is idempotent and an
unknown payment id answers 200 no-op. Payment metadata now carries
property: hqtui.com (plus account_id/email when a caller has them).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ralyodio
ralyodio merged commit a300e57 into main Oct 21, 2026
16 checks passed
@ralyodio
ralyodio deleted the fix/coinpay-webhook-nested branch October 21, 2026 12:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant