---
title: Payment Flow - Scanner (In-House)
tags: [flow, scanner, payment]
created: 2026-05-30
---

# Payment Flow — AMN Pay Scanner (In-House)

> **Last updated:** 2026-05-31 — documented backend `2.6.82` / scanner `0.1.7` capped accepted confirmation floors.

End-to-end payment flow using the in-house AMN Pay Scanner, replacing the Request Network integration. The scanner is a separate microservice; the backend talks to it over an internal HTTP API.

See also: [Scanner Architecture](../01%20-%20Architecture/Scanner%20Architecture.md), [Scanner API](../03%20-%20API%20Reference/Scanner%20API.md)

---

## 1. High-level sequence

```
Buyer                Backend              Scanner            Chain
  │                     │                    │                 │
  │  initiate payment   │                    │                 │
  │────────────────────►│                    │                 │
  │                     │ POST /intents      │                 │
  │                     │───────────────────►│                 │
  │                     │ 200 checkoutBlock  │                 │
  │                     │◄───────────────────│                 │
  │  checkoutBlock      │                    │                 │
  │◄────────────────────│                    │                 │
  │                     │                    │                 │
  │  sign + submit tx ──────────────────────────────────────►│
  │                     │                    │  (polling)      │
  │                     │                    │◄────────────────│
  │                     │                    │  log matched    │
  │                     │                    │  confirmations… │
  │                     │◄───────────────────│                 │
  │                     │  POST callbackUrl  │                 │
  │                     │  (webhook)         │                 │
  │                     │                    │                 │
  │  payment confirmed  │                    │                 │
  │◄────────────────────│                    │                 │
```

---

## 2. Step-by-step

### Step 1 — Backend creates an intent

When the buyer chooses a payment method (e.g. USDT on BSC), the backend calls:

```
POST http://scanner:8080/intents
Authorization: Bearer <SCANNER_API_KEY>

{
  "intentId":       "<payment._id>",
  "chainId":        56,
  "tokenAddress":   "0x55d398326f99059ff775485246999027b3197955",
  "destination":    "0xSellerWalletAddress",
  "amount":         "10000000000000000000",
  "callbackUrl":    "https://api.amn.gg/api/payment/amn-scanner/webhook",
  "callbackSecret": "<per-intent HMAC secret stored in payment doc>",
  "confirmations":  200
}
```

The scanner responds with a `checkoutBlock` that the backend passes to the frontend.

### Step 2 — Frontend shows checkout

The `checkoutBlock` contains everything the frontend needs to build the `ERC20FeeProxy.transferWithReferenceAndFee` calldata:

| Field | Used for |
|---|---|
| `proxyAddress` | contract to call |
| `tokenAddress` | ERC20 token |
| `destination` | `_to` param |
| `paymentReference` | `_paymentReference` param (8-byte reference) |
| `amountWei` | `_amount` param |
| `feeAmount` | `_feeAmount` param (always `"0"` currently) |
| `feeAddress` | `_feeAddress` param (always dead address) |

For Tron/TON the buyer sends a plain TRC20/Jetton transfer to `destination`; there is no proxy contract.

### Step 3 — Buyer submits transaction

The buyer signs and broadcasts the transaction using their wallet. The scanner independently monitors the chain and does not require the transaction hash.

### Step 4 — Scanner detects and confirms

**EVM path:**
1. `eth_getLogs` returns a `TransferWithReferenceAndFee` log matching `topicRef`
2. `validateLogMatchesIntent` verifies token address, destination, and amount
3. Intent moves to `confirming`; scanner waits for N blocks
4. Once `confirmationsRequired` blocks have been built on top, intent moves to `confirmed`. The scanner stores and reports the accepted threshold count, not an ever-growing live count.

**Tron path:**
1. TronGrid `Transfer` event matches `destination` (EVM-hex normalized)
2. Amount validated ≥ intent amount
3. Intent goes directly to `confirmed` (TronGrid returns only confirmed txs)

**TON path:**
1. TonCenter Jetton transfer matches `destination` (exact base64url) and `jetton_master_address`
2. Amount validated ≥ intent amount
3. Intent goes directly to `confirmed`

### Step 5 — Webhook delivery

The scanner POSTs to `callbackUrl` with:

```json
{
  "intentId": "...",
  "paymentReference": "0x...",
  "txHash": "0x...",
  "blockNumber": 39000010,
  "amount": "10000000000000000000",
  "token": "0x55d...",
  "chainId": 56,
  "confirmations": 200,
  "status": "confirmed"
}
```

Header `X-AMN-Signature` = `HMAC-SHA256(body, callbackSecret)`.

The backend verifies the signature, matches the `intentId` to a Payment record, and marks it paid. Backend `2.6.82+` treats scanner `status: "confirmed"` as final enough to run Transaction Safety Provider checks and persist `blockchain.confirmations`. The stored confirmation count comes from verifier evidence first, then the webhook payload, then the configured per-chain threshold fallback, but settled counts are capped at the accepted threshold so the UI can show values like `200+` instead of chasing the live chain height forever.

### Step 6 — Backend acknowledges

Backend returns a 2xx response. Scanner records `webhook_delivered_at` and the intent lifecycle ends.

---

## 3. Failure paths

### Webhook delivery failure

If the backend returns non-2xx or is unreachable, the scanner retries:

```
attempt 1: after 5 s
attempt 2: after 30 s
attempt 3: after 2 min
attempt 4: after 10 min
attempt 5: after 1 h
→ status = webhook_failed
```

`webhook_failed` intents are retried every `WEBHOOK_RETRY_HOURS` (default 6 h) and on `POST /admin/webhooks/retry`.

On startup the scanner reconciles any `confirmed` intents with `webhook_delivered_at IS NULL` (crash recovery).

### Intent expiry

Intents in `pending` or `confirming` status older than `INTENT_TTL_HOURS` (default 24 h) are moved to `expired` by a background ticker running every hour.

`confirming` intents can get stuck if a transaction is deep-reorganised and never re-included; the TTL frees the destination address for reuse.

### Amount underpayment

Transfers where the on-chain amount is less than `intent.Amount` are silently skipped. The intent remains `pending` until the TTL.

### Wrong token or destination

The EVM log decoder validates all three fields (token, destination, amount). Mismatches are logged as `REJECT` and skipped. The intent remains `pending`.

---

## 4. Key differences from Request Network integration

| Dimension | Request Network | AMN Pay Scanner |
|---|---|---|
| Dependency | RN SDK + API | None (direct RPC) |
| Payment reference | RN-generated | Internal HMAC derivation |
| EVM matching | By reference hash (RN) | By Topics[1] / topicRef (indexed) |
| Tron | Not supported | TRC20 Transfer events via TronGrid |
| TON | Not supported | Jetton transfers via TonCenter v3 |
| Confirmations | RN handled | Per-chain configurable |
| Webhook | RN webhook → backend adapter | Scanner → backend directly |
| State store | External (RN cloud) | Internal SQLite |
