calcom/cal.diy · error · HttpCode

Missing or invalid signature format

Error message

Missing or invalid signature format

What it means

Authenticity guard on the inbound webhook: the handler reads the signature from the `btcpay-sig`/`BTCPay-Sig` header and requires it to be a present string starting with `sha256=`. If the header is missing, not a string, or lacks the expected prefix, HttpCode 401 is thrown — the request is treated as unauthenticated before any payload parsing.

Source

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

  type: z.string(),
  timestamp: z.number(),
  storeId: z.string(),
  invoiceId: z.string(),
  metadata: z.object({}).optional(),
  manuallyMarked: z.boolean().optional(),
  overPaid: z.boolean(),
});
const SUPPORTED_INVOICE_EVENTS = ["InvoiceSettled", "InvoiceProcessing"];

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  try {
    if (req.method !== "POST") throw new HttpCode({ statusCode: 405, message: "Method Not Allowed" });
    const rawBody = await getRawBody(req);
    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" });

View on GitHub (pinned to 176037d0af)

Solutions

  1. In BTCPay Server, edit the webhook and confirm the secret is set and that signature delivery is enabled.
  2. Verify no proxy/CDN strips the `btcpay-sig` header (check casing and forwarding rules).
  3. Restrict the endpoint to BTCPay source IPs or require an ingress secret if exposed publicly.
  4. Log the inbound headers (names only) on 401 to confirm whether the header arrived under a different casing.

Example fix

// before
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" });

// after
const signature = req.headers["btcpay-sig"];
if (typeof signature !== "string" || !signature.startsWith("sha256="))
  throw new HttpCode({ statusCode: 401, message: "Missing or invalid signature format" });
Defensive patterns

Strategy: validation

Validate before calling

const sig = req.headers["btcpay-sig"];
if (typeof sig !== "string" || !sig.startsWith("sha256=")) {
  return res.status(401).json({ message: "Missing signature" });
}

Type guard

function isBTCPaySigHeader(v: unknown): v is string {
  return typeof v === "string" && v.startsWith("sha256=");
}

Try / catch

try {
  handler(req, res);
} catch (e) {
  if (e instanceof HttpCode && e.statusCode === 401) {
    return res.status(401).json({ message: e.message });
  }
  throw e;
}

Prevention

When it happens

Trigger: Webhook delivery where BTCPay Server did not attach the signature header (misconfigured webhook, signature disabled), the header name differs, or a non-BTCPay client posts to the endpoint.

Common situations: Webhook recreated in BTCPay without re-copying the secret/signature settings; reverse proxy stripping headers; the endpoint accidentally exposed and probed by scanners; BTCPay Server version that uses a different header casing not covered by the fallback.

Related errors


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