{"record":{"id":"8f95e03ffad1179c","repo":"calcom/cal.diy","slug":"cal-diy-store-id-mismatch","errorCode":null,"errorMessage":"Cal.diy: Store ID mismatch","messagePattern":"Cal\\.diy: Store ID mismatch","errorType":"http","errorClass":"HttpCode","httpStatus":400,"severity":"error","filePath":"packages/app-store/btcpayserver/api/webhook.ts","lineNumber":77,"sourceCode":"      return res.status(200).send({ message: \"Webhook received but ignored\" });\r\n\r\n    const bookingPaymentRepository = new BookingPaymentRepository();\r\n    const payment = await bookingPaymentRepository.findByExternalIdIncludeBookingUserCredentials(\r\n      data.invoiceId,\r\n      appConfig.type\r\n    );\r\n    if (!payment) throw new HttpCode({ statusCode: 404, message: \"Cal.diy: payment not found\" });\r\n    if (payment.success) return res.status(200).send({ message: \"Payment already registered\" });\r\n    const key = payment.booking?.user?.credentials?.[0].key;\r\n    if (!key) throw new HttpCode({ statusCode: 404, message: \"Cal.diy: credentials not found\" });\r\n\r\n    const parsedKey = btcpayCredentialKeysSchema.safeParse(key);\r\n    if (!parsedKey.success)\r\n      throw new HttpCode({ statusCode: 400, message: \"Cal.diy: Invalid BTCPay credentials\" });\r\n\r\n    const { webhookSecret, storeId } = parsedKey.data;\r\n    if (storeId !== data.storeId)\r\n      throw new HttpCode({ statusCode: 400, message: \"Cal.diy: Store ID mismatch\" });\r\n\r\n    const expectedSignature = signature.split(\"=\")[1];\r\n    const computedSignature = verifyBTCPaySignature(rawBody, expectedSignature, webhookSecret);\r\n\r\n    if (computedSignature.length !== expectedSignature.length) {\r\n      throw new HttpCode({ statusCode: 400, message: \"signature mismatch\" });\r\n    }\r\n    const isValid = crypto.timingSafeEqual(\r\n      Buffer.from(computedSignature, \"hex\"),\r\n      Buffer.from(expectedSignature, \"hex\")\r\n    );\r\n    if (!isValid) throw new HttpCode({ statusCode: 400, message: \"signature mismatch\" });\r\n\r\n    const traceContext = distributedTracing.createTrace(\"btcpayserver_webhook\", {\r\n      meta: { paymentId: payment.id, bookingId: payment.bookingId },\r\n    });\r\n    await handlePaymentSuccess({\r\n      paymentId: payment.id,\r","sourceCodeStart":59,"sourceCodeEnd":95,"githubUrl":"https://github.com/calcom/cal.diy/blob/176037d0afbe572f870a3c702985e7cd83fe6c0c/packages/app-store/btcpayserver/api/webhook.ts#L59-L95","documentation":"The webhook payload's `storeId` is compared against the `storeId` stored in the credential. A mismatch means the event came from a different BTCPay store than the one this user configured, so it is rejected with HttpCode 400 to prevent cross-store payment attribution.","triggerScenarios":"A single BTCPay Server has multiple stores and the webhook endpoint receives events from a store other than the one linked to the user's credential; the user reconfigured their store id in BTCPay without updating the Cal.diy credential; webhook URL shared across stores.","commonSituations":"Multi-store BTCPay accounts; webhook configured at server level rather than per-store; copy-paste error when entering the store id during setup; store recreated with a new id after deletion.","solutions":["Open the BTCPay integration settings in Cal.diy and update the store id to match the store that owns the invoice.","Confirm the BTCPay webhook is scoped to a single store, not server-wide.","If multiple stores must be supported, configure separate credentials/webhook endpoints per store.","Compare `data.storeId` from the payload against the value shown in BTCPay's store settings to identify the drift."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"if (storeId !== data.storeId) {\n  return res.status(400).json({ message: `Store mismatch: webhook=${data.storeId} credential=${storeId}` });\n}","typeGuard":null,"tryCatchPattern":"try {\n  processWebhook(data, credential);\n} catch (e) {\n  if (e instanceof HttpCode && /Store ID mismatch/.test(e.message)) {\n    return res.status(400).json({ message: e.message });\n  }\n  throw e;\n}","preventionTips":["Scope BTCPay webhooks per store, not server-wide.","Copy the store id from BTCPay's store settings into the credential verbatim.","Add a multi-store support matrix to onboarding docs.","Reject mismatched-store events with 400 so BTCPay doesn't retry indefinitely."],"tags":["webhook","validation","btcpay","multi-tenant","security"],"backgroundTag":null,"analyzedSha":"176037d0afbe572f870a3c702985e7cd83fe6c0c","analyzedAt":"2026-08-12T19:12:41.464Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}