hcengineering/platform · error
Invalid signature
Error message
Invalid signature
What it means
stripe.webhooks.constructEvent() threw while verifying the HMAC signature or parsing the payload, so the handler responds 403 'Invalid signature'. This covers signature mismatch, bad timestamp tolerance, and payload tampering.
Source
Thrown at services/payment/pod-payment/src/providers/stripe/webhook.ts:64
return
}
if (sig === undefined) {
ctx.error('Missing Stripe signature header')
res.status(400).json({ error: 'Missing signature' })
return
}
// Create Stripe instance for webhook verification
const stripe = new Stripe(stripeApiKey, { apiVersion: '2025-02-24.acacia' })
// Verify webhook signature and parse event
let event: Stripe.Event
try {
event = stripe.webhooks.constructEvent(rawBody, sig, webhookSecret)
} catch (err: any) {
ctx.error('Invalid Stripe webhook signature', { err })
res.status(403).json({ error: 'Invalid signature' })
return
}
// Route to appropriate handler based on event type
switch (event.type) {
case 'customer.subscription.created':
case 'customer.subscription.updated':
case 'customer.subscription.deleted': {
void handleSubscriptionUpdated(ctx, accountsUrl, serviceToken, event).catch((err) => {
ctx.error('Failed to process Stripe webhook event', { event, err })
})
break
}
case 'invoice.payment_succeeded':
case 'invoice.payment_failed': {
void (async () => {
try {
const subscriptionEvent = await createSubscriptionEventFromInvoiceEvent(ctx, stripe, event)View on GitHub (pinned to 63e28dc964)
Solutions
- Copy the exact whsec_... signing secret from the Stripe dashboard endpoint (or stripe listen output) into STRIPE_WEBHOOK_SECRET
- Ensure the raw Buffer body is passed unmodified to constructEvent
- Check server clock skew (NTP) if errors mention timestamp tolerance
- Confirm the secret belongs to the same endpoint (and mode: test vs live) that is sending events
Example fix
// before (wrong secret: API key) STRIPE_WEBHOOK_SECRET=sk_test_... // after STRIPE_WEBHOOK_SECRET=whsec_abc123_from_endpoint_settings
Defensive patterns
Strategy: try-catch
Validate before calling
if (!process.env.STRIPE_WEBHOOK_SECRET?.startsWith('whsec_')) {
throw new Error('STRIPE_WEBHOOK_SECRET must be the endpoint signing secret (whsec_...)')
} Try / catch
try {
const event = stripe.webhooks.constructEvent(rawBody, sig, webhookSecret)
} catch (err) {
ctx.error('Invalid Stripe webhook signature', { err })
res.status(403).json({ error: 'Invalid signature' })
return
} Prevention
- Use the endpoint's whsec_ secret, never the API key
- Pass the untouched raw Buffer to constructEvent
- Synchronize server clock (NTP) for timestamp tolerance
- Keep test/live endpoint secrets separate per environment
When it happens
Trigger: STRIPE_WEBHOOK_SECRET doesn't match the signing secret of the endpoint (whsec_...); body re-serialized before verification (loses raw bytes); clock skew beyond tolerance; using the secret of a different endpoint (each Stripe endpoint has its own secret).
Common situations: Copy-pasting the account's restricted key instead of the webhook signing secret; mixing test/live mode endpoints; API gateway re-encoding bodies.
Related errors
AI-assisted analysis of hcengineering/platform@63e28dc964 (2026-08-29).
Data as JSON: /api/errors/65bcdc86ceca15e8.
Report an issue: GitHub.