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

  1. Have the booking owner re-install the BTCPay Server integration so a credential with a populated `key` exists.
  2. Inspect the query to confirm credentials are filtered to the BTCPay app type so `[0]` is the right row.
  3. If the credential is intentionally gone, mark the payment as orphaned and return 200 to stop retries.
  4. 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

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


AI-assisted analysis of calcom/cal.diy@176037d0af (2026-08-12). Data as JSON: /api/errors/26bd618ae7d5a888. Report an issue: GitHub.