GitHub
PATH SETTLEMENT

SDK reference

Settlement

Issuing receipts, reading them, and verifying one produced by someone else.

issueReceipt(input)

Requires a credential.

const receipt = await path.issueReceipt({
  issuedBy: 'payee',
  request: '7fk2m9pq3vx8',
  via: { rail: 'book', asset_type: 'fiat', asset_code: 'USD' },
  received: { value: '5000', asset_type: 'fiat', asset_code: 'USD' },
  railReferences: [{ type: 'book_entry', value: 'LE-8891-2231' }],
});

receipt.reference; // "rcpt_9a41c8f2b731"
receipt.signature;
receipt.kid;
FieldNotes
issuedBy'payer' or 'payee'
requestOptional — a spontaneous payment has no request. The public reference from the link, never an internal id
viaThe capability used
railReferencesTyped by the rail. Required on 'settled'
status'pending' or 'settled'. Defaults to 'settled' on a payee receipt
attestationsOptional signed claims travelling with the receipt

Separate from marking a request paid, because payment and proof are two events that can be seconds or hours apart. Conflating them forces every integration to pretend every rail settles as fast as a signature.

Issue 'pending' when the rail confirms later. It is the only route to a signed statement that a payment failed — issue straight to 'settled' and the only document you can ever produce is one asserting success.


transitionReceipt(reference, input)

Requires the issuer's credential.

// the rail came back
await path.transitionReceipt('rcpt_9a41c8f2b731', {
  to: 'settled',
  railReferences: [{ type: 'book_entry', value: 'E1234…' }],
});

// or it did not
await path.transitionReceipt('rcpt_9a41c8f2b731', {
  to: 'failed',
  reasonCode: 'rail_unavailable',
});

Returns a new signed envelope. The one you were holding stays valid and stays readable. supersedes is the digest of that envelope.

FieldNotes
to'settled' | 'failed' | 'reversed'. pending → settled | failed, settled → reversed
reasonCodeRequired on 'failed'. Closed list — see PATH SETTLEMENT
reasonFree text. Switch on reasonCode, never on this

'reversed' follows 'settled' only. A failure never moved value; a reversal moved it and moved it back.


readReceiptChain(reference)

No credential.

const chain = await path.readReceiptChain('rcpt_9a41c8f2b731');

chain.status;                    // "failed"
chain.transitions.length;        // 2
chain.transitions[1].reason_code; // "rail_unavailable"

Each envelope in the chain verifies on its own. Worth reading rather than trusting the latest one alone: the current state says the money is there, the chain says whether it ever left, came back, or arrived on the second attempt.


readReceipt(reference)

No credential.

const receipt = await path.readReceipt('rcpt_9a41c8f2b731');

Public because a receipt is only useful if a third party can check it — a supplier, an auditor, the payer's own institution. Behind a credential, the only people who could verify a settlement would be the two who already agree about it.


Verifying someone else's

import { verifyAgainstIssuer } from '@pathprotocol/sdk';

const { valid, reason } = await verifyAgainstIssuer(receipt, 'https://api.other-member.com');

The key comes from the issuer's own discovery document. Not from whoever handed you the receipt, and not from a central registry: an operator vouching for another's key rebuilds the hierarchy the protocol avoids, and means compromising one operator compromises statements about others.

Offline

import { verifyEnvelope } from '@pathprotocol/sdk';

const valid = verifyEnvelope(receipt, cachedIssuerPublicKey);

Once you hold the key, verification needs no network at all. That is what lets a receipt be shown as a QR code and checked by someone with no account anywhere — the concrete form of finality is a verifiable state, not a promise.

Unknown key id

const { valid, reason } = await verifyAgainstIssuer(receipt, issuerUrl);
// reason: 'Issuer publishes no key with kid "op_example_2026_02"'

Reported distinctly from a bad signature, because it usually means a rotation you have not picked up. Refetch the issuer's document and try again before concluding anything.


The reserved field

receipt.route; // null — implementations still emit null

PIP-0005 defines the only shape route may take (legs, providers, eta_seconds). This SDK types it as ReceiptRoute | null and emits null. Carry the field through. Do not strip it, and do not reject an object because a counterpart populated it.

On this page