calcom/cal.diy · error · HttpCode
Cal.diy: credentials not found
Error message
Cal.diy: credentials not found
What it means
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.
Source
Thrown at packages/app-store/btcpayserver/api/webhook.ts:69
if (!signature || typeof signature !== "string" || !signature.startsWith("sha256="))
throw new HttpCode({ statusCode: 401, message: "Missing or invalid signature format" });
const webhookData = btcpayWebhookSchema.safeParse(JSON.parse(bodyAsString));
if (!webhookData.success) return res.status(400).json({ message: "Invalid webhook payload" });
const data = webhookData.data;
if (!SUPPORTED_INVOICE_EVENTS.includes(data.type))
return res.status(200).send({ message: "Webhook received but ignored" });
const bookingPaymentRepository = new BookingPaymentRepository();
const payment = await bookingPaymentRepository.findByExternalIdIncludeBookingUserCredentials(
data.invoiceId,
appConfig.type
);
if (!payment) throw new HttpCode({ statusCode: 404, message: "Cal.diy: payment not found" });
if (payment.success) return res.status(200).send({ message: "Payment already registered" });
const key = payment.booking?.user?.credentials?.[0].key;
if (!key) throw new HttpCode({ statusCode: 404, message: "Cal.diy: credentials not found" });
const parsedKey = btcpayCredentialKeysSchema.safeParse(key);
if (!parsedKey.success)
throw new HttpCode({ statusCode: 400, message: "Cal.diy: Invalid BTCPay credentials" });
const { webhookSecret, storeId } = parsedKey.data;
if (storeId !== data.storeId)
throw new HttpCode({ statusCode: 400, message: "Cal.diy: Store ID mismatch" });
const expectedSignature = signature.split("=")[1];
const computedSignature = verifyBTCPaySignature(rawBody, expectedSignature, webhookSecret);
if (computedSignature.length !== expectedSignature.length) {
throw new HttpCode({ statusCode: 400, message: "signature mismatch" });
}
const isValid = crypto.timingSafeEqual(
Buffer.from(computedSignature, "hex"),
Buffer.from(expectedSignature, "hex")
View on GitHub (pinned to 176037d0af)
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.
Defensive patterns
Strategy: validation
Validate before calling
const creds = payment.booking?.user?.credentials ?? [];
const btcpayCred = creds.find((c) => c.type === "btcpayserver");
if (!btcpayCred?.key) {
return res.status(200).json({ message: "Awaiting credential reconnection" });
} Type guard
function hasCredentialKey(c: unknown): c is { key: Record<string, unknown> } {
return !!c && typeof c === "object" && !!(c as any).key;
} Try / catch
try {
processPayment(data);
} catch (e) {
if (e instanceof HttpCode && /credentials not found/.test(e.message)) {
notifyUserToReconnectBTCPay(payment.booking.userId);
return res.status(200).json({ message: "Credential missing; user notified" });
}
throw e;
} Prevention
- 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.
When it happens
Trigger: 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`).
Common situations: 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.
Related errors
- Cal.diy: payment not found
- Booking with uid ${uid} not found
- Event type with uid ${uid} not found
- Payment not found
- signature mismatch
AI-assisted analysis of calcom/cal.diy@176037d0af (2026-08-12).
Data as JSON: /api/errors/26bd618ae7d5a888.
Report an issue: GitHub.