Skip to main content

Discover incoming payments by paymentId

What this lets you do

Find payments that have been sent against a specific paymentId you generated. The SDK's standard discoverNotes() flow handles this: it builds a tag-to-request map from your open requests and matches Note events whose indexed hint equals a request's tag (Poseidon([paymentId])), then decrypts each hit with that request's paymentPrivKey and marks the request fulfilled.

Payment-request discovery uses the request tag to narrow the event stream before decrypting. You don't call a separate scan function for payment requests. Instead, keep your generated requests loaded in the session and run normal discovery.

Constraints & limits

  • Discovery cost. Only events with a matching request tag are decrypted with that request's key. Prune fulfilled requests to keep the tag map small.
  • ASP note-events helps a lot. When aspUrl is configured, the SDK pulls the encrypted Note event stream from /{chainId}/public/note-events (plus RPC gap-fill) instead of chunking eth_getLogs itself.
  • Discovery scans from the sync cursor. Discovery walks events from fromBlock (if you pass it) or the NoteManager's persisted sync cursor up to the chain head. The request's createdAt is lifecycle metadata, not a scan lower bound. To avoid re-walking history, persist the NoteManager sync cursor across sessions. To backfill a request older than your cursor, pass an explicit fromBlock.
  • Request persistence is SDK-managed. The SDK persists open requests through its payment-request storage adapter and re-derives each request's paymentPrivKey from your viewing key, owner address, and paymentId on load, so you don't store the private key yourself. Load persisted requests on session build so discovery sees them.

What it unlocks next

  • Match payments to off-chain invoices automatically.
  • Trigger downstream business logic when an expected payment lands (e.g., release the deliverable, mark the invoice paid).
  • Generate a receipt for the matched payment: the noteSecret is decrypted as part of the discovery and can be embedded directly.

How to use it

await session.discoverNotes();

const acct = await session.exportAccount();
const myRequest = acct.paymentRequests?.find((r) => r.status === "FULFILLED");
const matched = myRequest
? acct.notes.filter((n) => myRequest.fulfilledByCommitments.includes(n.commitment))
: [];
for (const note of matched) {
console.log(`Payment received: ${note.value} of ${note.tokenId} (tx ${note.txHash})`);
}

Request tags: payment-request transfers use the request's public tag as the Note event hint. Anyone can see that an encrypted payload exists, but only the matching request key can decrypt it. Discovery matches events to open requests and marks matched requests fulfilled.

Behind the scenes

SDK call session.discoverNotes(). Payment-request matching is folded into the standard discovery flow when requests are registered with the session.
Event source ASP /{chainId}/public/note-events stream (with RPC gap-fill) when aspUrl is set, otherwise paginated eth_getLogs against the pool
Cryptography Tag match event.hint === request.tag, then X25519 ECDH(request.paymentPrivKey, event.ephemeralPubKey) yields the shared secret that decrypts the payload. Finally, check the decoded noteAddressHash is in the request's slot map