Skip to content

fix(payments): accept LNbits 1.x callback bodies - #1067

Open
Ferryx349 wants to merge 1 commit into
mainfrom
fix/lnbits-callback-body
Open

Ferryx349 wants to merge 1 commit into
mainfrom
fix/lnbits-callback-body

Conversation

@Ferryx349

@Ferryx349 Ferryx349 commented Nov 6, 2026 •

Copy link
Copy Markdown
Collaborator

Description

Problem

LNbits 1.x sends the payment object as a JSON string inside the webhook body, effectively double-encoding it. The route used Express's default strict JSON parser, which rejected the top-level string with 400. Even with an object body, the strict Zod schema rejected LNbits' extra payment fields.

As a result, paid invoices were only confirmed by the maintenance poll up to a minute later, while the invoice page still showed “waiting for payment”.

Fix

  • /callbacks/lnbits now uses json({ strict: false }), accepting both object and JSON-string bodies.
  • The controller parses string bodies and returns 400 Malformed body for invalid JSON.
  • The body schema is no longer strict; it validates payment_hash and strips LNbits' extra fields.
  • HMAC verification and invoice lookup from LNbits remain unchanged.

Related Issue

Closes - #1066

Motivation and Context

How Has This Been Tested?

Screenshots (if appropriate):

Types of changes

  • Non-functional change (docs, style, minor refactor)
  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

Checklist:

  • My code follows the code style of this project.
  • My change requires a change to the documentation.
  • I have updated the documentation accordingly.
  • I have read the CONTRIBUTING document.
  • I have added tests to cover my code changes.
  • I added a changeset, or this is docs-only and I added an empty changeset.
  • All new and existing tests passed.

Summary by CodeRabbit

  • New Features
    • LNbits callbacks now accept payment data sent as either a JSON object or a JSON-encoded string. Valid payment requests can include additional fields, which are ignored during validation.
  • Bug Fixes
    • Invalid JSON callback bodies return a clear malformed-body response instead of proceeding to invoice lookup.

@changeset-bot

changeset-bot Bot commented Nov 6, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 0832ffd

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
nostream Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai

coderabbitai Bot commented Nov 6, 2026 •

Copy link
Copy Markdown

Explain Change →

No actionable comments were generated in the recent review.

Walkthrough

The LNbits callback route now accepts JSON string bodies. The controller parses string bodies before validation, and the schema strips unknown fields while validating the payment data. Malformed JSON strings return 400 Malformed body.

Changes: LNbits callback body handling

Layer / File(s) Summary
Parse and validate callback bodies
src/routes/callbacks/index.ts, src/controllers/callbacks/lnbits-callback-controller.ts, src/schemas/lnbits-callback-schema.ts, test/unit/routes/callbacks.spec.ts, test/unit/controllers/callbacks/lnbits-callback-controller.spec.ts, test/unit/schemas/lnbits-callback-schema.spec.ts, .changeset/lnbits-callback-body.md
The /lnbits route accepts non-object JSON values. The controller parses string bodies, returns 400 Malformed body when parsing fails, and passes validated data to invoice processing. The schema strips unknown fields. Tests cover string and object bodies, extra fields, and malformed JSON. The changeset records the callback-body update.

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix

Possibly related issues

  • Issue 1066 describes LNbits 1.x callback bodies rejected by strict schema validation. This PR adds parsing for string bodies and strips extra fields during validation.
Merge Risk: Minimal · up to 0832f

The callback compatibility change is mergeable after normal checks; no specific remaining risk is established.

Security architecture risk: Low · up to 0832f

The callback accepts additional body formats while preserving HMAC checks, payment-hash validation and provider-side payment verification. No introduced authorization bypass was identified. Existing recovery limitations and unverified production concurrency leave limited residual uncertainty.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — The sensitive path remains invoice lookup, persistence, confirmation and notification in an application configured for LNbits. Additional body representations do not introduce new payment identities or authority: the same hash could already be submitted in a minimal object body.

Trust Boundaries and Controls

  • observed — The route rejects requests when payments are disabled or another processor is selected. The controller verifies a secret-derived HMAC and unexpired timestamp before inner-body processing. That HMAC authenticates the expiry, not the payment hash or body; this authority scope existed before the PR and is not widened by accepting another representation of the same hash.

Resilience and Maintainability Implications

  • observed — Confirmation retains a database-side confirmed_at guard and a serializable transaction, providing an existing control against repeating confirmation-side effects. The PR does not alter this control. Exact concurrent transaction outcomes were not verified against a production database, and notification remains outside the confirmation transaction.
Pre-merge checks: Passed 5

Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly describes the main change: accepting LNbits 1.x callback bodies.
Docstring Coverage Passed No eligible functions found in the changed files. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentat…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Full details: Docstring Coverage

Explanation

No eligible functions found in the changed files. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentation-only edits and deletions within surviving functions are included; unchanged diff context and fully deleted functions are excluded. Analyzed 0 functions across 6 files. (1 skipped: 1 unsupported.)

Finishing Touches

Generate docstrings

  • Commit to this branch
  • Create a new PR
Generate unit tests (beta)

  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

A rabbit checks the callback stream
JSON strings now join the team
Extra fields fall away with care
A valid hash goes onward there
“Malformed!” gets a four-oh-oh
Then off to invoice lookup we go!

Recent review info

Automatic approval policy

Commit: 0832ffdfea2f.

Setting Effective value Source
Automatic approvals Enabled Global configuration
Merge risk Minimal Global configuration
Security risk Not set Global configuration

Not set means no limit was configured after inheritance. Use @coderabbitai configuration to inspect the resolved settings and their sources.

Run configuration

  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 38d7ca49-a366-4f9d-975c-e4e674d2d8ec
Commits

Reviewing files that changed from the base of the PR and between 44f2e7b and 0832ffd.

Files selected for processing: 7

  • .changeset/lnbits-callback-body.md
  • src/controllers/callbacks/lnbits-callback-controller.ts
  • src/routes/callbacks/index.ts
  • src/schemas/lnbits-callback-schema.ts
  • test/unit/controllers/callbacks/lnbits-callback-controller.spec.ts
  • test/unit/routes/callbacks.spec.ts
  • test/unit/schemas/lnbits-callback-schema.spec.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 9 remain after this review.

@greptile-apps

greptile-apps Bot commented Nov 6, 2026 •

Copy link
Copy Markdown

RetriggerView in GreptileConfidence Score: 5/5

[Medium impact] The PR appears safe to merge.

Summary

Accepts LNbits callbacks as either plain objects or JSON-encoded strings.

  • LNbits 1.x callbacks can reach invoice confirmation.

No actionable issues found. Tests were inspected but not run.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A[LNbits callback] --> B[Check active provider]
  B --> C[Parse JSON, allowing strings]
  C --> D[Verify HMAC]
  D --> E{Body is a string?}
  E -->|Yes| F[Decode inner JSON]
  E -->|No| G[Check payment_hash]
  F --> G
  G --> H[Read invoice from LNbits]
  H --> I[Existing invoice update and confirmation]
Loading

Reviews (1) · Last reviewed commit: "fix(payments): accept LNbits 1.x callbac..." · Reviewed by Greptile

@coveralls

Copy link
Copy Markdown
Collaborator

Coverage Report for CI Build 42226652206

Coverage increased (+0.003%) to 77.575%

Details

  • Coverage increased (+0.003%) from the base build.
  • Patch coverage: 2 uncovered changes across 1 file (18 of 20 lines covered, 90.0%).
  • No coverage regressions found.

Uncovered Changes

File Changed Covered %
src/routes/callbacks/index.ts 7 5 71.43%
Total (3 files) 20 18 90.0%

Coverage Regressions

No coverage regressions found.


Coverage Stats

Coverage Status
Relevant Lines: 13143
Covered Lines: 10664
Line Coverage: 81.14%
Relevant Branches: 6906
Covered Branches: 4889
Branch Coverage: 70.79%
Branches in Coverage %: Yes
Coverage Strength: 704.92 hits per line

💛 - Coveralls

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants