WIP - not for production.
Paykit helps apps discover where someone can receive a payment through their Pubky identity. As a meta payment protocol, it also provides a layer for payment-related metadata such as receipts, Receipt Access, and future features like automated subscriptions.
A payee can publish public payment details under their Pubky public key, or share a Private Payment Envelope with another person over an Encrypted Link. Paykit handles the common Pubky storage layout, encrypted message formats, receipts, and language bindings needed for that exchange.
Wallets, payment processors, and apps keep control of payment execution, business rules, payment selection, local storage, key rotation, subscription logic, and timeouts. Paykit is the discovery and exchange layer those systems can integrate.
For canonical protocol vocabulary, see THESAURUS.md.
Paykit Protocol defines the domain rules, data model, and flows for payment discovery and exchange through Pubky Routing.
Paykit uses Pubky as its only network and storage backend. Public data is stored
on Pubky homeservers under paths owned by a Pubky public key, and private Paykit
messages are exchanged through pubky-noise.
Public Payment Endpoint Payloads are stored as separate files under:
/pub/paykit/v0/{payment_endpoint_identifier}
Public reads use pubky::PublicStorage; authenticated writes use
pubky::PubkySession. Missing files or directories are treated as absent data
rather than protocol errors.
Private Application Messages use pubky-noise. Paykit derives per-counterparty
private folders during the Encrypted Link Handshake, while pubky-noise owns
encryption, file naming, counters, and storage slots.
- Payment Method: the broad human/domain concept for how value can move, such as Bitcoin, Lightning, SEPA, ACH, or a card rail.
- Payment Endpoint Identifier: the machine-readable identifier for a
Payment Endpoint type, such as
btc-lightning-bolt12. - Payment Endpoint Payload: the receiving handle/details for that identifier, such as an address, invoice, offer, IBAN, tag, or JSON descriptor.
- Payment Endpoint: the pairing of a Payment Endpoint Identifier and a Payment Endpoint Payload.
- Payment List: a collection of Payment Endpoints published by a payee or shared privately with a counterparty.
- Private Payment Envelope: a versioned Private Application Message carrying a Payment Reference and a complete Payment List over an Encrypted Link.
- Receipt Access: an Event Message that lets a counterparty retrieve and decrypt an encrypted Receipt.
Paykit can describe many kinds of payment details as long as payer and payee understand the same Payment Endpoint Identifiers. The recommended identifier convention is documented in specs/payment-endpoint-identifier.md. The convention is recommended for interoperability, but the current library only enforces structural path-safety validation.
Public Payment Lists are discoverable by anyone who knows the payee's Pubky public key.
- The payee creates one or more Payment Endpoints.
- The payee writes each Payment Endpoint Payload under its Payment Endpoint Identifier.
- The payee shares their Pubky public key.
- A payer calls
get_payment_listorget_payment_endpointthrough the Paykit Library or a Language Binding.
Public Payment Lists are observable by anyone with the payee public key. Apps should avoid publishing reusable or correlation-sensitive Payment Endpoint Payloads unless that matches the payee's privacy model.
Private Payment Envelopes are shared only with a counterparty over an established Encrypted Link.
- The counterparties create an Encrypted Link with
initiate_encrypted_link/accept_encrypted_linkand advance it withadvance_handshake. - The payee builds a complete Private Payment Envelope containing a UUID-v4 Payment Reference and the counterparty-specific Payment List.
- The payee sends it with
set_private_payment_envelope. - The payer calls
get_private_payment_envelopeand receives the newest queued envelope, if one is available.
Private Payment Envelopes use Latest-State Message semantics: newer envelopes
supersede older queued envelopes of the same kind. The caller is responsible for
maintaining the complete payment_endpoints map and sending the full desired Payment List on
each update.
Paykit helps wallets and processors discover candidate Payment Endpoints. It does not execute payments or choose the final endpoint. The caller decides which Payment Endpoint to use according to its own Payment Selection Policy.
When an Encrypted Link exists, callers can prefer the latest Private Payment Envelope. If no Encrypted Link or Private Payment Envelope is available, callers can fall back to the payee's public Payment List when that is acceptable for the payment's privacy model.
If a payment attempt fails because an endpoint was consumed, expired, or changed, callers should re-fetch the relevant Payment Endpoint or Payment List and apply their own retry policy.
Payment Endpoint Payloads can represent static or interactive payment flows. Paykit transports the Payment Endpoint Payload; the payment-specific protocol is still implemented by the wallet or processor.
A Payment Endpoint Payload may point to a server, offer, API, or protocol flow that requires the payer to interact before a payment can be executed.
A Payment Endpoint Payload may also contain a static receiving detail such as an on-chain address, reusable offer, bank account detail, payment tag, or similar handle.
Paykit receipts are encrypted before storage. The payee stores the encrypted Receipt at the canonical homeserver path derived from the Payment Reference, then sends Receipt Access to the counterparty over the Encrypted Link.
Receipt Access uses Event Message semantics: every valid Receipt Access message matters and typed getters must preserve send order. Apps that perform irreversible side effects after receiving Receipt Access should persist their own handled/unhandled state alongside Encrypted Link snapshots.
Receipt Decryption Keys are sensitive. Callers must not log raw key material and should store it only in platform secure storage.
The Paykit Library is a stateless Rust library for interacting with Paykit Protocol data on Pubky. It is intended to be used inside wallets, payment processors, and apps that already own their payment execution logic.
For release history and upgrade notes, see CHANGELOG.md.
Wallets can use Paykit to publish their own receiving details, discover payment details for contacts or counterparties, exchange Private Payment Envelopes over an Encrypted Link, and receive encrypted receipts.
Payment processors can use Paykit to expose the Payment Endpoints they support, retrieve Payment Lists for payees, and apply their own Payment Selection Policy before executing a payment through their existing infrastructure.
Paykit currently does not:
- execute payments
- choose the final Payment Endpoint for a payer
- manage subscriptions
- maintain a stateful background service/runtime
- fetch profiles or contacts
- manage Pubky session creation, authorization scope, key rotation, or account recovery
These responsibilities remain with the integrating application, the Pubky SDK, or future higher-level Paykit components.
paykit-libis the canonical Rust Paykit Library. It consumes concrete Pubky SDK handles and keeps no global application state.paykit-ffiexposes UniFFI bindings for Swift and Kotlin.paykit-react-nativewraps the generated bindings for React Native.
get_payment_list fetches all public Payment Endpoints published by a payee.
The result is empty when the payee has not published any endpoints.
get_payment_endpoint fetches one Payment Endpoint Payload for a payee and a
Payment Endpoint Identifier. Missing files are returned as None.
set_payment_endpoint publishes or updates one Payment Endpoint Payload under
the caller's authenticated Pubky session.
remove_payment_endpoint removes a previously published Payment Endpoint.
initiate_encrypted_link, accept_encrypted_link, and advance_handshake
perform the Encrypted Link Handshake. Handshakes and established Encrypted Links
can be serialized and restored by callers that need restart recovery.
set_private_payment_envelope sends a complete Private Payment Envelope over an
Encrypted Link. The serialized envelope must fit within one pubky-noise
message.
get_private_payment_envelope returns the newest queued Private Payment
Envelope, if one is available. Older queued envelopes of the same kind are
superseded.
issue_receipt stores an encrypted Receipt and sends Receipt Access to the
counterparty. get_receipt_access returns every currently available Receipt
Access message in send order. decrypt_receipt decrypts a Receipt fetched by
the app from its Receipt Location.
Private Application Messages share one ordered encrypted stream. Supported message kinds that are received for a different typed helper are buffered in memory, but that buffer is not crash-durable unless the application persists and reconciles its own state alongside Encrypted Link snapshots. Unsupported syntactically valid message kinds are logged and dropped rather than buffered indefinitely.
set_payment_endpoint(session, identifier, payload): publish or update one public Payment Endpoint.remove_payment_endpoint(session, identifier): remove one public Payment Endpoint.get_payment_list(storage, payee): fetch the payee's public Payment List.get_payment_endpoint(storage, payee, identifier): fetch one public Payment Endpoint Payload.
initiate_encrypted_link(...)/accept_encrypted_link(...): start the Encrypted Link Handshake.advance_handshake(...): progress the handshake until it returns a completedEncryptedLink.serialize_encrypted_link(...)/restore_encrypted_link(...): snapshot and restore an established Encrypted Link.serialize_encrypted_link_handshake(...)/restore_encrypted_link_handshake(...): snapshot and restore an in-progress handshake.
set_private_payment_envelope(link, envelope): send a complete Private Payment Envelope over the Encrypted Link.get_private_payment_envelope(link): receive the newest queued Private Payment Envelope, if one is available.
issue_receipt(session, link, draft): store an encrypted Receipt and send Receipt Access over the Encrypted Link.get_receipt_access(link): receive all currently available Receipt Access messages in send order.decrypt_receipt(encrypted_json, key, location): decrypt a Receipt fetched by the app from its Receipt Location.
Example identifiers:
btc-bitcoin-p2tr
btc-lightning-bolt11
btc-lightning-bolt12
eur-sepa-iban
Example Payment Endpoint Payload:
{
"value": "lnurl1...",
"label": "primary lightning endpoint"
}cargo fmt
cargo clippy --all-targets --all-features
cargo test
cargo doc --no-depsPlatform bindings must be built for every target:
cd paykit-ffi
./build.sh all- First draft implementation of paykit library: https://github.com/pubky/paykit-pdk
- Product overview: https://docs.google.com/document/d/1Z1HHdxpkOtelOXJRgPldso4_-lchzs3NL_JqDxCdiu8/edit?pli=1&tab=t.0