<!-- tempo-docs-context -->
> Source: [/docs/guide/payments/transfer-memos](https://tempo.xyz/developers/docs/guide/payments/transfer-memos.md)
> Section: [Accounts](https://tempo.xyz/developers/docs/accounts.md). Create accounts, connect wallets, read balances, send and receive payments, and reconcile deposits.
> [Documentation index](https://tempo.xyz/developers/llms.txt) · [Docs skill](https://tempo.xyz/developers/SKILL.md)

> Tempo MCP: Use `search`, `find_pages`, `read_page`, and `code` at `https://mcp.tempo.xyz` for Tempo and related documentation.
> If an indexed page is missing or reflects an older site, read this site’s [documentation index](https://tempo.xyz/developers/llms.txt) and Markdown pages directly.

<span id="attach-a-transfer-memo" />

# Payment references

Attach a 32-byte transfer memo to a [TIP-20 payment](https://tempo.xyz/developers/get-started/stablecoins) to identify an invoice, order, or customer record. Store the reference with your payment record, then match it to confirmed transfer events.

<span id="transfer-memo-best-practices" />

<span id="use-consistent-memo-formats" />

<span id="keep-memos-under-32-bytes" />

## Encode a payment reference

Use a short, opaque ID such as `INV-12345`. Memos are public transaction data; keep customer details in your application.

Use the same encoding when sending a reference and searching for it. This helper encodes a UTF-8 string and pads it on the right with zero bytes to fit the `bytes32` field:

```ts [payment-reference.ts]
import { toHex } from 'viem'

export function encodePaymentReference(reference: string) {
  if (new TextEncoder().encode(reference).length > 32) {
    throw new Error('Payment references must fit in 32 UTF-8 bytes')
  }
  return toHex(reference, { size: 32 })
}
```

The limit is **32 bytes**, not 32 characters. `toHex` throws if the input exceeds the requested size; it does not truncate it. For a longer reference, keep the full value in your database and put a short lookup ID in the memo.

<span id="transfer-memo-implementation-steps" />

<span id="set-up-your-project-for-transfer-memos" />

<span id="send-a-transfer-with-memo" />

## Send a payment with a reference

<span id="transfer-memo-demo" />

Connect a test account and send a payment with a reference on Moderato testnet.

**Interactive demo: Transfer with Memo**

1. Connect
2. Add funds
3. Send payment with memo

Source: [tempoxyz/examples/tree/main/examples/payments](https://github.com/tempoxyz/examples/tree/main/examples/payments)

Use the funded Moderato test account from [Send a payment](https://tempo.xyz/developers/docs/guide/payments/send-a-payment#send-from-your-backend). Set `TEMPO_RECIPIENT` to an account you control and save the helper above as `payment-reference.ts`.

:::code-group
```ts [send-payment.ts]
import { getAddress, parseUnits } from 'viem'
import { encodePaymentReference } from './payment-reference'
import { client } from './viem.config'

const recipient = process.env.TEMPO_RECIPIENT
if (!recipient) throw new Error('Set TEMPO_RECIPIENT')

const { receipt } = await client.token.transferSync({
  token: '0x20c0000000000000000000000000000000000001',
  to: getAddress(recipient),
  amount: parseUnits('10', 6),
  memo: encodePaymentReference('INV-12345'),
})

console.log(receipt.transactionHash)
```

```ts [viem.config.ts]
import { tempoModerato } from 'viem/chains'
import { Account, createClient } from 'viem/tempo'

const privateKey = process.env.TEMPO_PRIVATE_KEY
if (!privateKey || !/^0x[0-9a-fA-F]{64}$/.test(privateKey)) {
  throw new Error('Set TEMPO_PRIVATE_KEY to your funded test account private key')
}

export const client = createClient({
  account: Account.fromSecp256k1(privateKey as `0x${string}`),
  chain: tempoModerato,
  feeToken: '0x20c0000000000000000000000000000000000001',
})
```
:::

Passing `memo` makes the SDK call `transferWithMemo`. For a browser integration, pass the same value to `Hooks.token.useTransferSync` in your [payment action](https://tempo.xyz/developers/docs/guide/payments/send-a-payment#add-a-payment-action-in-the-browser).

<span id="watch-for-transfers-with-memos" />

<span id="index-memos-for-efficient-queries" />

## Find payments by reference

The `TransferWithMemo` event indexes `memo`, so you can query the token contract for a specific reference. Set `TEMPO_RECIPIENT` to your receiving account and `TEMPO_FROM_BLOCK` to the first block in the range you want to inspect.

```ts [find-payment.ts]
import { getAddress } from 'viem'
import { tempoModerato } from 'viem/chains'
import { Abis, createClient } from 'viem/tempo'
import { encodePaymentReference } from './payment-reference'

const recipient = process.env.TEMPO_RECIPIENT
const fromBlock = process.env.TEMPO_FROM_BLOCK
if (!recipient || !fromBlock) {
  throw new Error('Set TEMPO_RECIPIENT and TEMPO_FROM_BLOCK')
}

const client = createClient({ chain: tempoModerato })
const logs = await client.getContractEvents({
  address: '0x20c0000000000000000000000000000000000001',
  abi: Abis.tip20,
  eventName: 'TransferWithMemo',
  args: {
    to: getAddress(recipient),
    memo: encodePaymentReference('INV-12345'),
  },
  fromBlock: BigInt(fromBlock),
  toBlock: 'latest',
})

console.log(logs)
```

A memo match identifies a candidate payment. [Verify the token, recipient, amount, and receipt](https://tempo.xyz/developers/docs/guide/payments/accept-a-payment#reconcile-payments-with-memos) before crediting it. Store the transaction hash and log index so replaying events cannot credit the same transfer twice.

<span id="transfer-memo-recipes" />

<span id="exchange-deposit-reconciliation" />

## Choose a reference for your workflow

For customer deposits, assign a short customer ID and ask the sender to include it. If the sender cannot supply a memo, use [customer deposit addresses](https://tempo.xyz/developers/docs/guide/payments/virtual-addresses) instead.

### Payroll batch payments

Encode a distinct payment reference for each call in a [batch payment](https://tempo.xyz/developers/docs/guide/payments/send-a-payment#batch-payment-transactions). Store that reference with the corresponding payroll record so you can reconcile each transfer, even though the batch shares one transaction hash.

<span id="refund-address-in-memo" />

### Link refunds to the original payment

Use an original payment ID or a separate refund ID as the memo. Keep the refund destination in the payment record and verify it before submitting the refund. An Ethereum-style address written as text takes 42 bytes and cannot fit in this 32-byte field.

## Transfer memo learning resources

* [Send a payment](https://tempo.xyz/developers/docs/guide/payments/send-a-payment) — Transfer stablecoins from a backend or browser application.
* [Receive a payment](https://tempo.xyz/developers/docs/guide/payments/accept-a-payment) — Verify incoming transfers and reconcile payment records.
* [TIP-20 Specification](https://tempo.xyz/developers/docs/protocol/tip20/spec) — Read the contract methods and event definitions.
