# Inbound Wires

Decide and post wires received from the Fed, and return them

## Overview

When you process a file holding a credit transfer (pacs.008, or pacs.009), Twisp decides which account to credit and posts the wire as a `WIRE_INBOUND` workflow execution. The execution id is the message id of the file's message.

```
            decision                    RETURN
(received) ─────────> SETTLE ─────────────────> RETURN
     │                  ▲                         ▲
     │ decision         │ SETTLE                  │ RETURN
     └──────────> PENDING ───────────────────────┘
```

A Fedwire credit is final when it arrives, so a wire settles at once. There is no settlement date to wait for.

## Decisioning

The configuration's [decisioning mode](https://www.twisp.com/docs/reference/wire/configuration.md#decisioning) decides each wire:

| Mode             | Decision                                                                                                                                                                                                                            |
| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `WEBHOOK`        | Your endpoint answers the create webhook                                                                                                                                                                                            |
| `PENDING`        | The wire is credited to the pending account as `PENDING`                                                                                                                                                                            |
| `ACCOUNT_LOOKUP` | Twisp evaluates the `src` to an `externalId` and settles the wire to that account. When no account has the `externalId`, the source fails, or the account is one of the configuration's accounts, the wire is returned with `AC03`. |

Every mode has the webhook contract: it is asked with the webhook request below and answers with a webhook response.

### Webhook Request

Twisp POSTs to the configured endpoint, signed with `x-twisp-signature` like the ACH webhooks:

```json
{
  "workflowTask": "CREATE",
  "workflowName": "WIRE.INBOUND",
  "executionId": "0b9e44a2-6d0c-5b3e-9f1a-2c3d4e5f6a7b",
  "configurationId": "5d1c6a0e-8b7f-4e2a-9c3d-2e1f0a9b8c7d",
  "fileId": "3f1e2d3c-4b5a-6978-8a9b-0c1d2e3f4a5b",
  "messageId": "0b9e44a2-6d0c-5b3e-9f1a-2c3d4e5f6a7b",
  "messageElement": "FedwireFundsCustomerCreditTransfer",
  "creditTransfer": {
    "msgId": "20261009B1QDRCQR000001",
    "creationTime": "2026-10-09T09:00:00-04:00",
    "settlementDate": "2026-10-09T00:00:00Z",
    "uetr": "8a562c67-ca16-48ba-b074-65581be6f011",
    "endToEndId": "Scenario01EtoEId001",
    "amountCents": 51000074,
    "currency": "USD",
    "localInstrument": "CTRC",
    "debtor": {
      "name": "Corporation A",
      "account": "92315266453",
      "address": { "streetName": "Avenue of the Fountains", "townName": "Phoenix", "country": "US" }
    },
    "creditor": {
      "name": "Corporation B",
      "account": "567876543",
      "address": { "streetName": "Desert View Street", "townName": "Bisbee", "country": "US" }
    },
    "debtorAgent": { "routingNumber": "011104238" },
    "creditorAgent": { "routingNumber": "021040078" },
    "instructingAgent": { "routingNumber": "011104238" },
    "instructedAgent": { "routingNumber": "021040078" },
    "remittance": ["Payment for invoice 4562"]
  }
}
```

`creditTransfer` is the normalized message. Its fields:

| Field                                                                 | Description                                                                                                |
| --------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
| `msgId`                                                               | The sender's IMAD                                                                                          |
| `creationTime`, `settlementDate`                                      | Message creation time and interbank settlement date                                                        |
| `uetr`, `endToEndId`, `instrId`                                       | Payment identifiers                                                                                        |
| `amountCents`, `currency`                                             | The amount in cents, and `USD`                                                                             |
| `localInstrument`                                                     | Such as `CTRC` (customer transfer) or `BTRS` (bank transfer)                                               |
| `debtor`, `creditor`                                                  | `name`, `account` (`accountIBAN` is true for an IBAN), `address`; for a pacs.009, `routingNumber` or `bic` |
| `debtorAgent`, `creditorAgent`, `instructingAgent`, `instructedAgent` | Banks: `routingNumber`, `bic`, `name`, `address`                                                           |
| `intermediaryAgents`                                                  | Banks between the instructed agent and the creditor agent, in order                                        |
| `remittance`                                                          | Unstructured remittance lines                                                                              |

Fields with empty values are omitted.

### Webhook Response

```json
{
  "action": "SETTLE",
  "accountId": "6b879478-27e0-4fde-9038-ef81b028bb51",
  "metadata": { "note": "matched by account number" }
}
```

| Field                       | Description                                                                                                                             |
| --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| `action`                    | `SETTLE`, `PENDING`, `RETURN` or `RETRY`                                                                                                |
| `accountId`                 | The account to credit, for `SETTLE` and `PENDING`. It cannot be the configuration's settlement, suspense, exception or pending account. |
| `reasonCode`, `reasonInfo`  | For `RETURN`: an ISO return reason code, such as `AC03` or `AC04`, and free text. The code defaults to `NARR`.                          |
| `metadata`, `entryMetadata` | Metadata for the transaction and the account entry                                                                                      |

`RETRY`, or a webhook that fails, retries the decision.

### Account Lookup

An `ACCOUNT_LOOKUP` source reads the request's `creditTransfer` as `creditTransfer`:

| Your `externalId` is                    | `src`                                                                                                                         |
| --------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| The plain account number                | `creditTransfer.creditor.account`                                                                                             |
| A string format                         | `'dda:' + creditTransfer.creditor.account`                                                                                    |
| A v5 UUID of routing and account number | `uuid.NewSHA1(uuid('<your namespace>'), bytes(creditTransfer.creditorAgent.routingNumber + creditTransfer.creditor.account))` |

A settled wire records the `externalId` it found as `metadata.accountLookup.externalId`.

## Posting

| Decision  | Ledger                                                                                                            |
| --------- | ----------------------------------------------------------------------------------------------------------------- |
| `SETTLE`  | Posts `SYS_WIRE_INBOUND_SETTLE_CR` to the account                                                                 |
| `PENDING` | Posts `SYS_WIRE_INBOUND_PENDING_CR` to the account's pending layer                                                |
| `RETURN`  | Credits the suspense account (`SYS_WIRE_INBOUND_SETTLE_CR`), then returns the wire (`SYS_WIRE_INBOUND_RETURN_DR`) |

If the credit cannot post, for example because a velocity control on the account refuses it or the account is locked, the wire is credited to suspense and returned with `NARR` and the error code as its reason.

The message's `detail` records the outcome, such as `settle` or `returned AC03 ACCOUNT NOT FOUND`.

## Resolving PENDING Wires

Settle or return a pending wire with [`Mutation.workflow.execute()`](https://www.twisp.com/docs/reference/graphql/mutations.md#workflow.execute) and code `WIRE_INBOUND`. The execution id is the message id.

```graphql
mutation SettlePendingWire {
  workflow {
    execute(
      input: {
        executionId: "0b9e44a2-6d0c-5b3e-9f1a-2c3d4e5f6a7b"
        code: "WIRE_INBOUND"
        task: "SETTLE"
        params: { accountId: "6b879478-27e0-4fde-9038-ef81b028bb51" }
      }
    ) {
      executionId
      output {
        state
      }
    }
  }
}
```

| Task     | From      | Parameters                                                                         | Ledger                                                                                            |
| -------- | --------- | ---------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
| `SETTLE` | `PENDING` | `accountId` (settles to another account), `effective`, `metadata`, `entryMetadata` | Voids the pending credit (`SYS_WIRE_INBOUND_PENDING_VOID_DR`), posts `SYS_WIRE_INBOUND_SETTLE_CR` |
| `RETURN` | `PENDING` | `reasonCode`, `reasonInfo`                                                         | Voids the pending credit                                                                          |
| `RETURN` | `SETTLE`  | `reasonCode`, `reasonInfo`                                                         | Posts `SYS_WIRE_INBOUND_RETURN_DR` to the account the wire settled to                             |

## Returns

Every `RETURN` queues the wire. The next [`generateFile`](https://www.twisp.com/docs/reference/wire/file-operations.md#generating-outbound-files) writes a pacs.004 that returns the full amount to the original sender, with the return's `reasonCode` and `reasonInfo`. Without a code, the pacs.004 uses `NARR`.

Only a wire received as a credit transfer (pacs.008 or pacs.009) can be returned, because its pacs.004 is built from that message. `RETURN` fails with `BAD_REQUEST` for any other wire, such as an unmatched return credited to the exception account, and changes nothing.

## Duplicates and Unmatched Returns

- A credit transfer whose UETR an earlier inbound wire already has is recorded as `IGNORED` and does not post.
- A pacs.004 that matches none of your outbound wires is posted as an inbound wire to the configuration's exception account. Its message `detail` reads `unmatched return credited to the exception account`. It has no credit transfer to send back, so `RETURN` refuses it with `BAD_REQUEST`. Resolve the funds with your own transactions.
