Skip to content

fix(webhooks): require HMAC verification before order mutations - #20

Open
SashaMIT wants to merge 1 commit into
circlefin:masterfrom
SashaMIT:fix/webhook-require-hmac
Open

fix(webhooks): require HMAC verification before order mutations#20
SashaMIT wants to merge 1 commit into
circlefin:masterfrom
SashaMIT:fix/webhook-require-hmac

Conversation

@SashaMIT

@SashaMIT SashaMIT commented Aug 8, 2026

Copy link
Copy Markdown

Summary

POST /api/webhooks/payments drives order lifecycle (Paid / Refunded / Canceled / …) through the Supabase service-role client, but HMAC verification was left commented out:

// Signature verification (uncomment once WEBHOOK_SECRET is set in env).
// const secret = process.env.WEBHOOK_SECRET;
// if (secret) { … }

That is fail-open: any caller who can reach the endpoint and supply a payment salt can forge lifecycle transitions without WEBHOOK_SECRET. README already lists WEBHOOK_SECRET as required setup and documents SCP signing.

This PR:

  1. Returns 503 when WEBHOOK_SECRET is unset (fail closed)
  2. Verifies x-scp-signature as HMAC-SHA256 over the raw body with length-checked timingSafeEqual
  3. Parses JSON only after verification
  4. Updates README (no more “uncomment the block”)

Operator routes that write order status without SCP remain unchanged.

Test plan

  • With WEBHOOK_SECRET unset → 503
  • With secret set, wrong/missing x-scp-signature401
  • With valid HMAC over body → existing salt lookup / status patch path unchanged
  • npm run lint on touched file

Made with Cursor

Payment webhook HMAC verification was commented out while the handler
still uses the Supabase service-role client to mark orders Paid /
Refunded / Canceled. Fail closed when WEBHOOK_SECRET is unset, verify
x-scp-signature with timing-safe compare, and parse the verified body.
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