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
- In BTCPay Server, edit the webhook and confirm the secret is set and that signature delivery is enabled.
- Verify no proxy/CDN strips the `btcpay-sig` header (check casing and forwarding rules).
- Restrict the endpoint to BTCPay source IPs or require an ingress secret if exposed publicly.
- 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
- Always set a webhook secret when creating the BTCPay webhook.
- Ensure proxies forward the `btcpay-sig` header unchanged.
- Restrict webhook endpoint ingress to BTCPay IPs where possible.
- Log inbound header names (not values) on 401 to debug missing headers cases.
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
- signature mismatch
- Cal.diy: Store ID mismatch
- Invoice not found
- Cal.diy: payment not found
- Cal.diy: credentials not found
AI-assisted analysis of calcom/cal.diy@176037d0af (2026-08-12).
Data as JSON: /api/errors/7030df7bb77979a7.
Report an issue: GitHub.