{"record":{"id":"c505431b6b95cfda","repo":"calcom/cal.diy","slug":"invoice-amount-does-not-match-payment-amount","errorCode":null,"errorMessage":"invoice amount does not match payment amount","messagePattern":"invoice amount does not match payment amount","errorType":"http","errorClass":"HttpCode","httpStatus":400,"severity":"error","filePath":"packages/app-store/alby/api/webhook.ts","lineNumber":89,"sourceCode":"      },\n    });\n\n    if (!payment) throw new HttpCode({ statusCode: 204, message: \"Payment not found\" });\n    const key = payment.booking?.user?.credentials?.[0].key;\n    if (!key) throw new HttpCode({ statusCode: 204, message: \"Credentials not found\" });\n\n    const parseCredentials = albyCredentialKeysSchema.safeParse(key);\n    if (!parseCredentials.success) {\n      console.error(parseCredentials.error);\n      throw new HttpCode({ statusCode: 500, message: \"Credentials not valid\" });\n    }\n\n    const credentials = parseCredentials.data;\n\n    const albyInvoice = await parseInvoice(bodyAsString, parsedHeaders, credentials.webhook_endpoint_secret);\n    if (!albyInvoice) throw new HttpCode({ statusCode: 204, message: \"Invoice not found\" });\n    if (albyInvoice.amount !== payment.amount) {\n      throw new HttpCode({ statusCode: 400, message: \"invoice amount does not match payment amount\" });\n    }\n\n    const traceContext = distributedTracing.createTrace(\"alby_webhook\", {\n      meta: { paymentId: payment.id, bookingId: payment.bookingId },\n    });\n    return await handlePaymentSuccess({\n      paymentId: payment.id,\n      bookingId: payment.bookingId,\n      appSlug: \"alby\",\n      traceContext,\n    });\n  } catch (_err) {\n    const err = getServerErrorFromUnknown(_err);\n    console.error(`Webhook Error: ${err.message}`);\n    return res.status(err.statusCode).send({\n      message: err.message,\n      stack: IS_PRODUCTION ? undefined : err.cause?.stack,\n    });","sourceCodeStart":71,"sourceCodeEnd":107,"githubUrl":"https://github.com/calcom/cal.diy/blob/176037d0afbe572f870a3c702985e7cd83fe6c0c/packages/app-store/alby/api/webhook.ts#L71-L107","documentation":"Thrown when the verified Alby invoice's amount (in satoshis) differs from the stored payment.amount. This is a tamper/idempotency guard: a validly-signed invoice whose value does not match what cal.com recorded, or a unit mismatch between satoshis and the stored amount.","triggerScenarios":"payment.amount was stored in a different unit than satoshis (e.g. cents or currency minor units); invoice amount changed between creation and payment; a stale payment row was reused for a regenerated invoice.","commonSituations":"Currency-conversion bug converting fiat to sats incorrectly; amount stored as cents while the invoice is denominated in sats; concurrent invoice regeneration producing a different amount.","solutions":["Verify the unit contract: the value passed to LightningAddress.requestInvoice({ satoshi }) must equal what is stored in payment.amount.","Reject and log the mismatch; do not mark the booking paid.","Ensure each new invoice creates a new payment row rather than reusing an old one."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"if (albyInvoice.amount !== payment.amount) {\n  // do not mark paid; log both amounts and the payment uid for forensics\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Keep payment.amount in the same unit (satoshi) used for requestInvoice.","Create a fresh payment row for each regenerated invoice.","Treat any mismatch as potential tampering and alert."],"tags":["alby","webhook","amount-mismatch","tamper-detection"],"backgroundTag":null,"analyzedSha":"176037d0afbe572f870a3c702985e7cd83fe6c0c","analyzedAt":"2026-08-12T19:12:41.464Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}