{"record":{"id":"65bcdc86ceca15e8","repo":"hcengineering/platform","slug":"invalid-signature-65bcdc","errorCode":null,"errorMessage":"Invalid signature","messagePattern":"Invalid signature","errorType":"http","errorClass":null,"httpStatus":403,"severity":"error","filePath":"services/payment/pod-payment/src/providers/stripe/webhook.ts","lineNumber":64,"sourceCode":"      return\n    }\n\n    if (sig === undefined) {\n      ctx.error('Missing Stripe signature header')\n      res.status(400).json({ error: 'Missing signature' })\n      return\n    }\n\n    // Create Stripe instance for webhook verification\n    const stripe = new Stripe(stripeApiKey, { apiVersion: '2025-02-24.acacia' })\n\n    // Verify webhook signature and parse event\n    let event: Stripe.Event\n    try {\n      event = stripe.webhooks.constructEvent(rawBody, sig, webhookSecret)\n    } catch (err: any) {\n      ctx.error('Invalid Stripe webhook signature', { err })\n      res.status(403).json({ error: 'Invalid signature' })\n      return\n    }\n\n    // Route to appropriate handler based on event type\n    switch (event.type) {\n      case 'customer.subscription.created':\n      case 'customer.subscription.updated':\n      case 'customer.subscription.deleted': {\n        void handleSubscriptionUpdated(ctx, accountsUrl, serviceToken, event).catch((err) => {\n          ctx.error('Failed to process Stripe webhook event', { event, err })\n        })\n        break\n      }\n      case 'invoice.payment_succeeded':\n      case 'invoice.payment_failed': {\n        void (async () => {\n          try {\n            const subscriptionEvent = await createSubscriptionEventFromInvoiceEvent(ctx, stripe, event)","sourceCodeStart":46,"sourceCodeEnd":82,"githubUrl":"https://github.com/hcengineering/platform/blob/63e28dc96483967b2fc21c881b3f1023c1de7718/services/payment/pod-payment/src/providers/stripe/webhook.ts#L46-L82","documentation":"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.","triggerScenarios":"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).","commonSituations":"Copy-pasting the account's restricted key instead of the webhook signing secret; mixing test/live mode endpoints; API gateway re-encoding bodies.","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"],"exampleFix":"// before (wrong secret: API key)\nSTRIPE_WEBHOOK_SECRET=sk_test_...\n// after\nSTRIPE_WEBHOOK_SECRET=whsec_abc123_from_endpoint_settings","handlingStrategy":"try-catch","validationCode":"if (!process.env.STRIPE_WEBHOOK_SECRET?.startsWith('whsec_')) {\n  throw new Error('STRIPE_WEBHOOK_SECRET must be the endpoint signing secret (whsec_...)')\n}","typeGuard":null,"tryCatchPattern":"try {\n  const event = stripe.webhooks.constructEvent(rawBody, sig, webhookSecret)\n} catch (err) {\n  ctx.error('Invalid Stripe webhook signature', { err })\n  res.status(403).json({ error: 'Invalid signature' })\n  return\n}","preventionTips":["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"],"tags":["security","webhook","hmac","signature","stripe"],"backgroundTag":"webhook-signature-verification-failed","analyzedSha":"63e28dc96483967b2fc21c881b3f1023c1de7718","analyzedAt":"2026-08-29T15:21:27.377Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}