> ## 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.

# Bridge Integration

> Connect Bridge to ingest stablecoin transfer evidence via webhooks and polling.

[Bridge](https://bridge.xyz) is a stablecoin payments infrastructure provider. A `BridgeIntegration` connects your Bridge account to ReconLayer so that transfer events — funding, on-chain movement, and payouts — are normalized into `FlowLeg`s and matched against your `PaymentIntent`s automatically.

<Note>
  Bridge evidence reaches ReconLayer through two of the [three ingestion paths](/integrations/ingestion-flows#the-three-ways-evidence-enters-reconlayer): inbound webhooks (real-time) and integration polling (catch-up / reconciliation).
</Note>

## Creating a Bridge integration

```bash theme={null}
curl -X POST https://api.reconlayer.com/v1/bridge-integrations \
  -H "Authorization: Bearer $RECONLAYER_API_KEY" \
  -H "x-organization-id: $ORG_ID" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Acme Bridge — Production",
    "integrationKey": "acme_bridge_main",
    "apiKey": "sk_bridge_live_...",
    "webhookBaseUrl": "https://api.reconlayer.com",
    "eventCategories": ["transfer"],
    "pollingEnabled": true
  }'
```

<ParamField body="name" type="string" required>Display name shown in the dashboard.</ParamField>

<ParamField body="integrationKey" type="string" required>
  Lowercase, snake\_case identifier (`^[a-z0-9_]+$`), unique to this integration. Used to build the inbound webhook URL and resolve organization context on incoming webhooks.
</ParamField>

<ParamField body="apiKey" type="string" required>Your Bridge API key, sent as `Api-Key` on calls to the Bridge API (`https://api.bridge.xyz/v0`). Never returned by the API — responses only include `hasApiKey: true`.</ParamField>

<ParamField body="webhookBaseUrl" type="string" required>
  **Must be `https://`.** ReconLayer derives the full webhook URL by appending `/webhooks/bridge/{integrationKey}` to this base (path normalized to avoid double slashes).
</ParamField>

<ParamField body="eventCategories" type="string[]" default="[&#x22;transfer&#x22;]">
  At least one of: `customer`, `kyc_link`, `liquidation_address.drain`, `static_memo.activity`, `transfer`, `virtual_account.activity`, `bridge_wallet.activity`, `card_account`, `card_transaction`, `card_withdrawal`, `posted_card_account_transaction`, `external_account`.
</ParamField>

<ParamField body="pollingEnabled" type="boolean" default="true">Whether the background poll loop should include this integration.</ParamField>

On creation, ReconLayer:

1. Builds the webhook URL: `${webhookBaseUrl}/webhooks/bridge/${integrationKey}`.
2. Calls the Bridge API to register a webhook for the requested `eventCategories` (`POST /webhooks` on `https://api.bridge.xyz/v0`, with an `Idempotency-Key` and `event_epoch: 'webhook_creation'`).
3. Stores the resulting `webhookId`, `webhookUrl`, and `webhookStatus` on the `BridgeIntegrationDetail`.

Response (`BridgeIntegrationDetail`):

```json theme={null}
{
  "id": "bint_01h...",
  "organizationId": "org_01h...",
  "name": "Acme Bridge — Production",
  "integrationKey": "acme_bridge_main",
  "webhookId": "wh_01h...",
  "webhookUrl": "https://api.reconlayer.com/webhooks/bridge/acme_bridge_main",
  "webhookStatus": "active",
  "eventCategories": ["transfer"],
  "hasApiKey": true,
  "pollingEnabled": true,
  "lastTransferSyncedAt": null,
  "createdAt": "2026-06-14T09:00:00.000Z",
  "updatedAt": "2026-06-14T09:00:00.000Z"
}
```

`webhookStatus` mirrors Bridge's own status enum: `disabled`, `active`, `deleted`.

See [Create a Bridge Integration](/api-reference/integrations/create-a-bridge-integration).

<Warning>
  If `webhookBaseUrl` is not `https://`, the request fails with `400 invalid_request` (`BridgeIntegrationValidationError`: *"Bridge webhook base URL must use https."*). Bridge integration calls that fail upstream surface as `502 bridge_upstream_error` with an `upstreamStatus` reflecting Bridge's HTTP status.
</Warning>

## Managing integrations

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

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

# Update event categories or polling
curl -X PATCH https://api.reconlayer.com/v1/bridge-integrations/bint_01h... \
  -H "Authorization: Bearer $RECONLAYER_API_KEY" \
  -H "x-organization-id: $ORG_ID" \
  -H "Content-Type: application/json" \
  -d '{ "eventCategories": ["transfer", "external_account"], "pollingEnabled": true }'
```

<Note>
  If you change `webhookBaseUrl` while the integration's `webhookStatus` is `active`, the request fails with `400` (*"Disable the Bridge integration before changing its webhook URL."*) — disable first, update, then re-enable.
</Note>

### Enable / disable

```bash theme={null}
curl -X POST https://api.reconlayer.com/v1/bridge-integrations/bint_01h.../enable \
  -H "Authorization: Bearer $RECONLAYER_API_KEY" \
  -H "x-organization-id: $ORG_ID"

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

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

## Receiving webhooks

Bridge sends events to `POST /webhooks/bridge/{integrationKey}`. ReconLayer verifies the `X-Webhook-Signature` (RSA-SHA256, 10-minute replay tolerance) before running matching — see [Inbound Webhooks](/integrations/webhooks#provider-webhooks-bridge) for the full signature verification flow and error responses.

```bash theme={null}
curl -X GET https://api.reconlayer.com/v1/webhook-endpoints \
  -H "Authorization: Bearer $RECONLAYER_API_KEY" \
  -H "x-organization-id: $ORG_ID"
```

## Webhook normalization

Every Bridge webhook payload becomes a `RawRecord` with `source: 'webhook'`, `sourceType: 'psp_report'`, `provider: 'bridge'`, and `integrationKey` set to your integration's key. `normalizedType` is taken from `event_type` (falling back to `event_category`, then `'bridge.webhook'`), and `normalizedId` is the Bridge transfer ID (falling back to `sourceRef`).

From a single webhook, ReconLayer can produce **up to two `FlowLeg`s**:

<AccordionGroup>
  <Accordion title="provider_transfer leg (phase: source)">
    Created whenever the payload carries a Bridge transfer ID. Its `status` is derived from `event_object_status` / the transfer's `state` via this mapping:

    | Bridge state                              | `FlowLeg.status` |
    | ----------------------------------------- | ---------------- |
    | `funds_received`, `payment_processed`     | `confirmed`      |
    | `returned`, `refunded`, `canceled`        | `reversed`       |
    | `error`, `undeliverable`, `refund_failed` | `failed`         |
    | anything else                             | `pending`        |

    `FlowLegReference`s captured: `bridge_event_id`, `bridge_event_type`, `external_reference` (from Bridge's `client_reference_id`), `customer_id` (from `on_behalf_of`), `destination_account_id`.
  </Accordion>

  <Accordion title="onchain_transfer leg (phase: transfer)">
    Created when the payload includes a `txHash`, or when the source/destination payment rail is `polygon` or `ethereum`. Uses the same Bridge-state-to-status mapping above.

    `chainId` is derived from the payment rail:

    | Payment rail | `chainId` |
    | ------------ | --------- |
    | `ethereum`   | `1`       |
    | `polygon`    | `137`     |

    `FlowLegReference`s captured: `tx_hash`, `bridge_transfer_id`, `external_reference`. Includes `fromAddress`, `toAddress`, and `tokenAddress` when present in the Bridge payload.
  </Accordion>
</AccordionGroup>

## Polling

In addition to (or instead of) webhooks, ReconLayer can poll Bridge for transfer state directly:

```bash theme={null}
curl -X POST https://api.reconlayer.com/v1/bridge-integrations/bint_01h.../poll-transfer \
  -H "Authorization: Bearer $RECONLAYER_API_KEY" \
  -H "x-organization-id: $ORG_ID" \
  -H "Content-Type: application/json" \
  -d '{ "transferId": "transfer_01h..." }'
```

<ParamField body="transferId" type="string" required>The Bridge transfer ID to fetch via `GET /transfers/:id` on the Bridge API.</ParamField>

Response (`BridgeTransferPollResponse`):

```json theme={null}
{
  "transferId": "transfer_01h...",
  "transferState": "payment_processed",
  "evidence": {
    "accepted": true,
    "duplicate": false,
    "rawRecordId": "rr_01h...",
    "flowLegIds": ["fl_01h..."],
    "matchedCaseIds": ["case_01h..."],
    "signatureValid": true,
    "source": "api",
    "sourceRef": "transfer_01h...:2026-06-14T09:30:00Z"
  }
}
```

A polled transfer is wrapped as a synthetic webhook payload (`event_category: 'transfer'`, `event_type: 'bridge.transfer.polled'`) and run through the **same normalization** described above, with `source: 'api'`, `signatureValid: true` always, and `sourceRef` set to `{transferId}:{updatedAt|createdAt|state}`. `normalizedType` becomes `bridge.transfer.{state}` (or `bridge.transfer.snapshot` if no state is available).

Re-polling the same transfer is deduplicated by comparing the transfer's `updated_at`/`created_at`/`state` (whichever is set first) against the value already recorded — if nothing changed, the existing record is returned as a duplicate rather than reprocessed.

See [Poll a Bridge Transfer](/api-reference/integrations/poll-a-bridge-transfer).

### Background poll loop

When `pollingEnabled: true`, the Bridge poll loop (`apps/api/src/bridge-poller.ts`) periodically calls `pollActiveBridgeIntegrations` across all active, polling-enabled integrations and feeds any new/changed transfers through `ingestEvidence`, using actor `system:bridge_poller`.

| Environment variable         | Default | Purpose                                                                             |
| ---------------------------- | ------- | ----------------------------------------------------------------------------------- |
| `BRIDGE_POLL_INTERVAL_MS`    | `60000` | Interval between poll cycles. Set to `0` or less to disable the poll loop entirely. |
| `BRIDGE_POLL_TRANSFER_LIMIT` | `25`    | Maximum transfers fetched per integration per cycle.                                |

## Next steps

* [Inbound Webhooks](/integrations/webhooks) — signature verification details and the canonical evidence endpoints for providers without a native adapter.
* [Ingestion Flows](/integrations/ingestion-flows) — how Bridge evidence flows into the matching engine.
* [On-chain Integrations](/integrations/onchain) — for chains/networks beyond what a Bridge `onchain_transfer` leg captures.
