# Import a note received out-of-band

> Claim a note someone sent you in out-of-band mode (you have the noteSecret but it's not encrypted on-chain).

## What this lets you do

Manually import a [note](/concepts/notes) into your local [`NoteManager`](/sdk/note-manager) when the sender delivered it [out-of-band](/concepts/discoverable-vs-oob). This is common when the recipient hasn't [registered a viewing key](register-viewing-key): there is no encrypted payload on-chain to discover, so the sender hands you the `noteSecret` directly via a secure channel, often using a [payment receipt](generate-receipt).

## Constraints & limits

-   **You need the full note material:** [commitment](/concepts/commitments-and-nullifiers), value, tokenId, label, ownerAddress, noteSecret, noteAddressHash. If any field is missing, the SDK rejects the import.
-   **The commitment must actually be on-chain** at the configured pool. Importing a fake or never-published note makes it appear locally, but the first spend will fail.
-   **Status is forced to `PENDING`:** `importReceivedNote` always stamps the note `PENDING`, regardless of any status you pass in. It is not spendable until a `discoverNotes()` sync confirms it on-chain and promotes it to `ACTIVE`. That happens when discovery finds the note's commitment among the outputs of the transfer that created it, or when the [ASP](/concepts/asp-attestation) reports its label approved.
-   **Owner-binding:** the note's `ownerAddress` must be the address your current session was built for. A mismatch rejects the import immediately with `InvalidNoteError`; the note is never stored.

## What it unlocks next

-   [Spend the note](/operations/transfer) via a private transfer.
-   [Withdraw](/operations/withdraw) the note's value to a public address.
-   Show the imported note in your UI immediately as `PENDING`. It becomes spendable once the next discovery sync promotes it to `ACTIVE`.

## How to use it

```ts

// You have a receipt JSON from the sender:
const receipt = JSON.parse(receiptText);

// createdAtBlock must hold the commitment's on-chain timestamp:
// PoolVault.commitments(commitment) stores block.timestamp, and spend
// proofs recompute the state-tree leaf from it. (discoverNotes() resolves
// this automatically; for a manual import, read it from the pool.)
const createdAtTimestamp = await client.readContract({
    address: receipt.pool.poolAddress,
    abi: parseAbi(["function commitments(uint256) view returns (uint256)"]),
    functionName: "commitments",
    args: [BigInt(receipt.note.commitment)],
});

await session.importReceivedNote({
    commitment:      receipt.note.commitment,
    value:           receipt.note.value,
    tokenId:         receipt.note.tokenId,
    label:           receipt.note.label,
    ownerAddress:    receipt.note.ownerAddress,
    noteSecret:      receipt.note.noteSecret,
    noteAddressHash: receipt.note.noteAddressHash,
    status:          NoteStatus.PENDING,
    createdAtBlock:  toHex(createdAtTimestamp),
    spentAtBlock:    null,
    txHash:          receipt.payment.txHash,
    isOwned:         true,
});
```

**Why this matters:** import-from-receipt is the bridge that makes the no-registration UX work. Recipients don't need to set anything up on-chain before they can claim a payment: the sender sends them a JSON receipt over a secure channel, they import it, and their balance updates.

## Behind the scenes

SDK call `session.importReceivedNote(note)`
No on-chain effect A local-only mutation of NoteManager state, followed by a persistent-storage flush.
Verification At import time, the SDK recomputes the noteAddressHash and commitment from the imported fields and checks they bind together (a field-internal consistency check, not an on-chain comparison). Any mismatch throws `InvalidNoteError` immediately, before the note is stored.
