calcom/cal.diy · error · HttpCode

Cal.diy: payment not found

Error message

Cal.diy: payment not found

What it means

After parsing the webhook payload, the handler looks up the booking payment by `data.invoiceId` (BTCPay invoice id) via `BookingPaymentRepository.findByExternalIdIncludeBookingUserCredentials`. If no payment row exists for that invoice id under the BTCPay app type, HttpCode 404 is thrown — the system received a payment event it has no local record of.

Source

Thrown at packages/app-store/btcpayserver/api/webhook.ts:66

    const bodyAsString = rawBody.toString();

    const signature = req.headers["btcpay-sig"] || req.headers["BTCPay-Sig"];
    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" });
    }

View on GitHub (pinned to 176037d0af)

Solutions

  1. Confirm the `invoiceId` in the webhook payload corresponds to a payment created by this Cal.diy instance (query `BookingPayment` by external id).
  2. If redelivery is expected post-cleanup, return 200 acknowledged instead of 404 to stop BTCPay retrying.
  3. Ensure webhooks are configured per-environment (sandbox vs production stores).
  4. Verify `appConfig.type` matches the `appId` used when the payment was stored.
Defensive patterns

Strategy: validation

Validate before calling

const existing = await bookingPaymentRepository.findByExternalIdIncludeBookingUserCredentials(invoiceId, appConfig.type);
if (!existing) {
  return res.status(200).json({ message: "No local payment for this invoice" });
}

Try / catch

try {
  await handleWebhook(data);
} catch (e) {
  if (e instanceof HttpCode && e.statusCode === 404 && /payment not found/.test(e.message)) {
    return res.status(200).json({ message: "Ignored: unknown invoice" });
  }
  throw e;
}

Prevention

When it happens

Trigger: BTCPay sends an `InvoiceSettled`/`InvoiceProcessing` event for an invoice that was never recorded locally: invoice created in BTCPay directly, payment row deleted, race where the event arrives before `PaymentService.create()` committed, or a webhook from a different Cal.diy instance.

Common situations: Manual invoice creation in BTCPay UI; webhook redelivery long after the booking/payment row was purged; multi-instance setup where webhooks hit the wrong environment; test/sandbox BTCPay store firing against production DB.

Related errors


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