> ## Documentation Index
> Fetch the complete documentation index at: https://docs.reconlayer.com/llms.txt
> Use this file to discover all available pages before exploring further.

# On-chain Integrations

> Ingest stablecoin and crypto transfer evidence from chain indexers via polling, push, or direct evidence submission.

On-chain integrations connect ReconLayer to a blockchain indexer or RPC provider so that confirmed transfers — stablecoin payouts, on-chain settlements, treasury movements — become `FlowLeg`s of type `onchain_transfer` and are matched against your `PaymentIntent`s.

<Note>
  EVM is currently the only supported `networkFamily`. `normalizeOnchainEvidenceByFamily` and `normalizeOnchainIntegrationTransferIngestByFamily` switch on `chainFamily`/`networkFamily` and throw for any value other than `'evm'`.
</Note>

## Two ways to bring on-chain evidence in

| Path                                                                      | When to use it                                                                                                                                                                                                     |
| ------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **On-chain integration** (`POST /v1/onchain-integrations` + `.../ingest`) | You run (or operate) an indexer that watches specific addresses on a specific network, and want ReconLayer to track sync state (`lastTransferSyncedAt`) per integration.                                           |
| **Direct evidence submission** (`POST /v1/evidence/onchain`)              | A one-off or ad-hoc submission — e.g. from your own backend after observing a transaction — without registering a persistent integration. See [Inbound Webhooks](/integrations/webhooks#post-v1-evidence-onchain). |

Both paths converge on the same EVM normalization and the same `ingestEvidence` pipeline described in [Ingestion Flows](/integrations/ingestion-flows).

## Creating an on-chain integration

```bash theme={null}
curl -X POST https://api.reconlayer.com/v1/onchain-integrations \
  -H "Authorization: Bearer $RECONLAYER_API_KEY" \
  -H "x-organization-id: $ORG_ID" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Polygon USDC Treasury",
    "provider": "alchemy",
    "networkFamily": "evm",
    "networkId": "137",
    "apiKey": "alch_...",
    "integrationKey": "polygon_usdc_treasury",
    "pollingEnabled": true,
    "pushEnabled": false,
    "watchedAddresses": ["0x1111111111111111111111111111111111111111"],
    "startBlock": 55000000
  }'
```

<ParamField body="name" type="string" required>Display name.</ParamField>
<ParamField body="provider" type="string" required>Your indexer/RPC provider name, e.g. `"alchemy"`, `"infura"`, `"custom"`.</ParamField>
<ParamField body="networkFamily" type="string" default="evm">Currently must be `"evm"`.</ParamField>
<ParamField body="networkId" type="string" required>Network/chain identifier as a string, e.g. `"1"` (Ethereum) or `"137"` (Polygon). Stored as `networkId` and stamped into ingested leg metadata.</ParamField>
<ParamField body="apiKey" type="string" required>Credential for your indexer/provider. Never returned — responses only include `hasApiKey: true`.</ParamField>
<ParamField body="integrationKey" type="string">Pattern `^[a-z0-9:_-]+$` (case-insensitive). Auto-generated if omitted.</ParamField>
<ParamField body="pollingEnabled" type="boolean" default="true">Whether ReconLayer (or your poller) can call `.../ingest` with `source: "poll"`.</ParamField>
<ParamField body="pushEnabled" type="boolean" default="false">Whether `.../ingest` accepts `source: "push"` calls (e.g. from a webhook-driven indexer).</ParamField>
<ParamField body="watchedAddresses" type="string[]" default="[]" required>Addresses this integration is responsible for monitoring.</ParamField>
<ParamField body="startBlock" type="number">Optional starting block height for backfill/sync purposes (0 to 2^53-1).</ParamField>

<Warning>
  At least one of `pollingEnabled` / `pushEnabled` must be `true` — creating or updating an integration with both `false` returns `400 invalid_request` (*"Enable polling or push ingestion before saving the on-chain integration."*).
</Warning>

Response (`OnchainIntegrationDetail`):

```json theme={null}
{
  "id": "ocint_01h...",
  "organizationId": "org_01h...",
  "provider": "alchemy",
  "networkFamily": "evm",
  "networkId": "137",
  "name": "Polygon USDC Treasury",
  "integrationKey": "polygon_usdc_treasury",
  "status": "disabled",
  "hasApiKey": true,
  "pollingEnabled": true,
  "pushEnabled": false,
  "watchedAddressCount": 1,
  "watchedAddresses": ["0x1111111111111111111111111111111111111111"],
  "startBlock": 55000000,
  "lastTransferSyncedAt": null,
  "createdAt": "2026-06-14T09:00:00.000Z",
  "updatedAt": "2026-06-14T09:00:00.000Z"
}
```

See [Create an On-chain Integration](/api-reference/integrations/create-an-on-chain-integration).

## Managing integrations

```bash theme={null}
# List
curl https://api.reconlayer.com/v1/onchain-integrations \
  -H "Authorization: Bearer $RECONLAYER_API_KEY" \
  -H "x-organization-id: $ORG_ID"

# Get detail
curl https://api.reconlayer.com/v1/onchain-integrations/ocint_01h... \
  -H "Authorization: Bearer $RECONLAYER_API_KEY" \
  -H "x-organization-id: $ORG_ID"

# Update watched addresses
curl -X PATCH https://api.reconlayer.com/v1/onchain-integrations/ocint_01h... \
  -H "Authorization: Bearer $RECONLAYER_API_KEY" \
  -H "x-organization-id: $ORG_ID" \
  -H "Content-Type: application/json" \
  -d '{ "watchedAddresses": ["0x1111...", "0x2222..."] }'

# Enable / disable
curl -X POST https://api.reconlayer.com/v1/onchain-integrations/ocint_01h.../enable \
  -H "Authorization: Bearer $RECONLAYER_API_KEY" \
  -H "x-organization-id: $ORG_ID"

curl -X POST https://api.reconlayer.com/v1/onchain-integrations/ocint_01h.../disable \
  -H "Authorization: Bearer $RECONLAYER_API_KEY" \
  -H "x-organization-id: $ORG_ID"
```

See [List On-chain Integrations](/api-reference/integrations/list-on-chain-integrations), [Get On-chain Integration Detail](/api-reference/integrations/get-on-chain-integration-detail), [Update an On-chain Integration](/api-reference/integrations/update-an-on-chain-integration), [Enable an On-chain Integration](/api-reference/integrations/enable-an-on-chain-integration), and [Disable an On-chain Integration](/api-reference/integrations/disable-an-on-chain-integration).

## Ingesting a transfer

`POST /v1/onchain-integrations/{onchainIntegrationId}/ingest` is how your poller or push-based indexer hands a discovered transfer to ReconLayer. The integration must be `active`, and the requested `source` mode must be enabled on the integration (`pollingEnabled` for `source: "poll"`, `pushEnabled` for `source: "push"`).

```bash theme={null}
curl -X POST https://api.reconlayer.com/v1/onchain-integrations/ocint_01h.../ingest \
  -H "Authorization: Bearer $RECONLAYER_API_KEY" \
  -H "x-organization-id: $ORG_ID" \
  -H "Content-Type: application/json" \
  -d '{
    "source": "poll",
    "txHash": "0xABCDEF1234567890abcdef1234567890ABCDEF1234567890abcdef12345678",
    "chainId": 137,
    "fromAddress": "0x1111111111111111111111111111111111111111",
    "toAddress": "0x2222222222222222222222222222222222222222",
    "tokenAddress": "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174",
    "externalReference": "INV-2026-0099",
    "status": "confirmed",
    "amount": { "amount": "1000.00", "currency": "USDC" },
    "occurredAt": "2026-06-14T09:30:00Z",
    "payload": { "raw": "indexer transaction payload" }
  }'
```

<ParamField body="source" type="string" default="poll">`"poll"` or `"push"` — determines which `pollingEnabled`/`pushEnabled` flag is checked, and the `actor` recorded on audit events (`system:onchain_integration_poll` or `system:onchain_integration_push`).</ParamField>
<ParamField body="txHash" type="string">Transaction hash. Lowercased during normalization.</ParamField>
<ParamField body="sourceRef" type="string">Either `sourceRef` or `txHash` is required, for deduplication.</ParamField>
<ParamField body="chainId" type="number">Numeric chain ID, e.g. `137` for Polygon.</ParamField>
<ParamField body="fromAddress" type="string">Sender address (lowercased).</ParamField>
<ParamField body="toAddress" type="string">Recipient address (lowercased).</ParamField>
<ParamField body="tokenAddress" type="string">Token contract address (lowercased), if applicable.</ParamField>
<ParamField body="externalReference" type="string">Matched against `PaymentIntent.externalReference`.</ParamField>
<ParamField body="status" type="string" default="confirmed">`pending`, `confirmed`, `failed`, `reversed`, or `missing`.</ParamField>
<ParamField body="amount" type="object" required>`{ "amount": string, "currency": string }`.</ParamField>
<ParamField body="paymentIntentId" type="string">Link directly to a known `PaymentIntent`.</ParamField>
<ParamField body="references" type="array">Additional `{ type, value }` `FlowLegReference` entries, merged with the auto-generated `tx_hash`/`external_reference` references.</ParamField>
<ParamField body="metadata" type="object">Arbitrary metadata, merged with `chainFamily: "evm"` and `networkId` (from the integration).</ParamField>
<ParamField body="payload" type="object" required>The raw transaction/event payload — stored verbatim on the `RawRecord`.</ParamField>

Response (`OnchainIntegrationTransferIngestResponse`):

```json theme={null}
{
  "networkFamily": "evm",
  "networkId": "137",
  "source": "poll",
  "evidence": {
    "accepted": true,
    "duplicate": false,
    "rawRecordId": "rr_01h...",
    "flowLegIds": ["fl_01h..."],
    "matchedCaseIds": ["case_01h..."],
    "signatureValid": null,
    "source": "api",
    "sourceRef": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef12345678"
  }
}
```

See [Ingest On-chain Transfer Evidence](/api-reference/integrations/ingest-on-chain-transfer-evidence).

## EVM normalization details

Both the integration-ingest path and the direct `POST /v1/evidence/onchain` path call `normalizeEvmTransfer`, which:

* Lowercases `txHash`, `fromAddress`, `toAddress`, and `tokenAddress` via hex normalization.
* Produces a `FlowLeg` with `type: 'onchain_transfer'`, `phase: 'transfer'`.
* Builds `FlowLegReference`s for `tx_hash` (from the normalized `txHash`) and `external_reference` (from `externalReference`), merged with any explicit `references` you pass.
* Stamps `metadata.chainFamily: 'evm'` always, and `metadata.networkId` (from the integration's configured `networkId`) for integration-ingest — plus whatever custom `metadata` you provide.

The resulting `RawRecord` has `source: 'api'`, `sourceType: 'onchain_report'`, `normalizedType: 'flow_leg'`, and `normalizedId` set to the normalized `txHash` (falling back to `sourceRef` if no `txHash` is present).

```mermaid theme={null}
flowchart LR
    A["POST /v1/onchain-integrations/:id/ingest\n(source: poll | push)"] --> N[normalizeEvmOnchainTransferIngest]
    B["POST /v1/evidence/onchain"] --> M[normalizeEvmOnchainEvidence]
    N --> E[normalizeEvmTransfer]
    M --> E
    E --> R["RawRecord (sourceType: onchain_report)\n+ FlowLeg (onchain_transfer, phase: transfer)"]
    R --> I[ingestEvidence]
    I --> Match[Matching engine]
```

## Next steps

* [Inbound Webhooks](/integrations/webhooks#post-v1-evidence-onchain) — the direct `POST /v1/evidence/onchain` path for one-off submissions.
* [Bridge Integration](/integrations/bridge#webhook-normalization) — Bridge webhooks can also produce `onchain_transfer` legs for `ethereum`/`polygon` rails.
* [Ingestion Flows](/integrations/ingestion-flows) — how on-chain evidence flows into the matching engine and `ReconciliationCase` updates.
