# Discover incoming payments by paymentId

> Find out when a request has been paid: discovery tag-matches incoming events and decrypts them with the request key.

## 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](../operations/payment-requests) 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](../operations/manage-notes).

## 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`](../sdk/note-manager)'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](../concepts/keys), 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](../operations/generate-receipt) for the matched payment: the noteSecret is decrypted as part of the discovery and can be embedded directly.

## How to use it

```ts
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
