{"record":{"id":"26bd618ae7d5a888","repo":"calcom/cal.diy","slug":"cal-diy-credentials-not-found","errorCode":null,"errorMessage":"Cal.diy: credentials not found","messagePattern":"Cal\\.diy: credentials not found","errorType":"http","errorClass":"HttpCode","httpStatus":404,"severity":"error","filePath":"packages/app-store/btcpayserver/api/webhook.ts","lineNumber":69,"sourceCode":"    if (!signature || typeof signature !== \"string\" || !signature.startsWith(\"sha256=\"))\r\n      throw new HttpCode({ statusCode: 401, message: \"Missing or invalid signature format\" });\r\n\r\n    const webhookData = btcpayWebhookSchema.safeParse(JSON.parse(bodyAsString));\r\n    if (!webhookData.success) return res.status(400).json({ message: \"Invalid webhook payload\" });\r\n\r\n    const data = webhookData.data;\r\n    if (!SUPPORTED_INVOICE_EVENTS.includes(data.type))\r\n      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","sourceCodeStart":51,"sourceCodeEnd":87,"githubUrl":"https://github.com/calcom/cal.diy/blob/176037d0afbe572f870a3c702985e7cd83fe6c0c/packages/app-store/btcpayserver/api/webhook.ts#L51-L87","documentation":"The booking payment was found, but `payment.booking?.user?.credentials?.[0].key` is falsy — i.e. the user who owns the booking has no BTCPay credential stored (or the credential row has no `key`). Without the stored credential the handler cannot retrieve the `webhookSecret` to verify the signature, so it aborts with HttpCode 404.","triggerScenarios":"The user disconnected/deleted their BTCPay Server credential after the booking was created; the credential row exists but `key` is null/empty; the query's `credentials[0]` is a different app's credential (the relation isn't filtered by `type`).","commonSituations":"User removed the payment integration between booking creation and webhook delivery; credential migration left `key` empty; `findByExternalIdIncludeBookingUserCredentials` returns credentials not scoped to `btcpayserver` so `[0]` is the wrong credential.","solutions":["Have the booking owner re-install the BTCPay Server integration so a credential with a populated `key` exists.","Inspect the query to confirm credentials are filtered to the BTCPay app type so `[0]` is the right row.","If the credential is intentionally gone, mark the payment as orphaned and return 200 to stop retries.","Audit credential-deletion paths to prevent removing a credential that still has pending payments."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"const creds = payment.booking?.user?.credentials ?? [];\nconst btcpayCred = creds.find((c) => c.type === \"btcpayserver\");\nif (!btcpayCred?.key) {\n  return res.status(200).json({ message: \"Awaiting credential reconnection\" });\n}","typeGuard":"function hasCredentialKey(c: unknown): c is { key: Record<string, unknown> } {\n  return !!c && typeof c === \"object\" && !!(c as any).key;\n}","tryCatchPattern":"try {\n  processPayment(data);\n} catch (e) {\n  if (e instanceof HttpCode && /credentials not found/.test(e.message)) {\n    notifyUserToReconnectBTCPay(payment.booking.userId);\n    return res.status(200).json({ message: \"Credential missing; user notified\" });\n  }\n  throw e;\n}","preventionTips":["Filter the credentials relation by app type when fetching.","Block credential deletion when pending payments exist.","Re-validate credentials on a schedule and warn users before expiry.","Return 200 for credential-missing cases to stop BTCPay retry storms."],"tags":["webhook","credentials","payment","btcpay","data-integrity"],"backgroundTag":null,"analyzedSha":"176037d0afbe572f870a3c702985e7cd83fe6c0c","analyzedAt":"2026-08-12T19:12:41.464Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}