Skip to main content
Your user wants to pay someone’s on-chain address but only has Lightning. Pay-through turns a Lightning payment into an on-chain payout to any address.

How it works

The payer pays a hold invoice. The maker broadcasts the payout to the destination address. Then the hold settles. If the maker never pays out, the hold is released.
The payout leg has no hash lock: the maker controls it. The payer’s Lightning payment stays held until the payout is broadcast, but the payout itself is not enforced on chain. Use the HTLC swaps where that matters.

Pay

The maker must have pay-through enabled. The client needs no WebAssembly initialization:
TypeScript
transaction.mempool reports the payout broadcast; invoice.settled reports that the hold settled.

What create checks

It refuses an order that differs from the request: another destination, another invoiceAmount, less than the requested payoutAmount, or another asset. It decodes the invoice and refuses one whose amount or payment hash differs from the order. Amounts are safe JavaScript integers; a larger value is rejected, not rounded.

Idempotent creation and recovery

create(request, { idempotencyKey }) sends the key as an Idempotency-Key header. Use a random UUIDv4 generated with a CSPRNG, persist it before sending, and treat it as a secret: replaying it can recover the order’s swapAuth.
  • Call supportsIdempotency() before the first create, against the same maker. Older makers can silently ignore the key, so a successful create does not prove support.
  • To recover a lost response, call create again with the same request, key, maker URL, and credentials.
  • On PAY_THROUGH_IDEMPOTENCY_IN_FLIGHT, retry the identical request and key with bounded backoff (a few attempts over one to two minutes).
  • Never change the key while a timeout, 5xx, or in-flight outcome is unresolved, and never pay a second invoice to recover the first.
Other error codes exported by the subpath: PAY_THROUGH_IDEMPOTENCY_MISMATCH (the request differs from the original), PAY_THROUGH_IDEMPOTENCY_SCOPE_MISMATCH (different credentials), and PAY_THROUGH_IDEMPOTENCY_EXPIRED (the original order expired or is no longer awaiting payment: reconcile it before creating a new one). Requests time out after timeoutMs (30 seconds by default) and accept an optional signal. PayThroughClient also takes an orgKey for partner attribution.

If something goes wrong

  • The invoice expired unpaid: start a new payment intent with a new request and a new key.
  • The payer paid but no payout appears: check pay.status(order.id). The hold settles only after the payout is broadcast; until then the payment can still be released.