Internal user counterparty
Quick start

OneView documentation

Internal user counterparty

Intra-bank transfers between two customers and how to send both legs.

Internal user counterparty

Use counterpartyType: "INTERNAL_USER" when the other party is another customer of your institution, for example an intra-bank transfer between two accounts you hold. accountName is required. A request without it is rejected with counterparty.accountName is required for this counterparty type.

FieldRequiredWhat it meansWhy it matters for monitoring
counterpartyTypeYesINTERNAL_USER.Identifies an on-us movement.
accountNameYesThe other customer's account name.Shown to analysts and used for screening when no personal or company name is sent.
customerUniqueIdNo, but strongly recommendedThe other customer's ID, in the same form as the account holder's.Links the transaction to the other customer's profile, so analysts see both of your customers involved.
accountNumberNoThe other customer's account.Velocity rules can group by the receiving account. It can also match your blacklist.
countryNoTwo-letter ISO 3166-1 code.Country rules.

Sending both legs of an intra-bank transfer

An intra-bank transfer debits one customer and credits another. Send it as two transactions with the same transactionReference:

  1. An OUTBOUND transaction whose account holder is the sender and whose counterparty is the receiver.
  2. An INBOUND transaction whose account holder is the receiver and whose counterparty is the sender.

OneView identifies a transaction by its reference together with its direction, so the two legs are separate transactions and are not treated as a replay or a conflict. Each leg is evaluated against the customer it belongs to: the sender's outgoing activity and the receiver's incoming activity.

Set isInternal: true on both legs.

Leg 1: OUTBOUND from the sender

POST /api/transactions
Authorization: Bearer <oneview-service-account-api-key>
Content-Type: application/json

{
  "transactionReference": "INT-20260921-000501",
  "direction": "OUTBOUND",
  "accountHolderType": "CUSTOMER",
  "paymentMethod": "INTERNAL_TRANSFER",
  "channel": "MOBILE_APP",
  "status": "POSTED",
  "isInternal": true,
  "isCrossBorder": false,
  "amount": "250000.00",
  "currency": "NGN",
  "narration": "Monthly allowance",
  "executedAt": "2026-09-21T07:55:30Z",
  "accountHolder": {
    "customerUniqueId": "CUS-100231",
    "accountNumber": "0012345678",
    "accountName": "Ada Demo Okafor",
    "riskLevel": "LOW"
  },
  "counterparty": {
    "counterpartyType": "INTERNAL_USER",
    "customerUniqueId": "CUS-100877",
    "accountNumber": "0012345690",
    "accountName": "Tunde Demo Okafor",
    "country": "NG"
  },
  "telemetry": {
    "deviceId": "dev-7f3a9c2e51d04b88",
    "ipAddress": "192.0.2.24",
    "country": "NG"
  }
}

Leg 2: INBOUND to the receiver

POST /api/transactions
Authorization: Bearer <oneview-service-account-api-key>
Content-Type: application/json

{
  "transactionReference": "INT-20260921-000501",
  "direction": "INBOUND",
  "accountHolderType": "CUSTOMER",
  "paymentMethod": "INTERNAL_TRANSFER",
  "channel": "MOBILE_APP",
  "status": "POSTED",
  "isInternal": true,
  "isCrossBorder": false,
  "amount": "250000.00",
  "currency": "NGN",
  "narration": "Monthly allowance",
  "executedAt": "2026-09-21T07:55:30Z",
  "accountHolder": {
    "customerUniqueId": "CUS-100877",
    "accountNumber": "0012345690",
    "accountName": "Tunde Demo Okafor",
    "riskLevel": "LOW"
  },
  "counterparty": {
    "counterpartyType": "INTERNAL_USER",
    "customerUniqueId": "CUS-100231",
    "accountNumber": "0012345678",
    "accountName": "Ada Demo Okafor",
    "country": "NG"
  }
}

Points to note:

  • The reference is identical and only direction differs. Sending the same reference and direction again with a different body is rejected with IDEMPOTENCY_CONFLICT, so never reuse one leg's reference and direction for the other leg.
  • The device telemetry belongs to the sender's session, so it is sent only on the outbound leg.
  • Each leg receives its own decision. Your system decides how to combine them. For example, if either leg is BLOCK, stop the whole transfer. See Decision responses.
  • If your core banking system posts only the debit side, send only the OUTBOUND leg. The receiver's activity is then not monitored for this transfer.

Response

Each leg is a distinct transaction with its own transaction.id and its own decision, even though they share a transactionReference. This is leg 1 (OUTBOUND, the sender):

{
  "status": true,
  "message": "Transaction evaluated",
  "data": {
    "transaction": {
      "id": "1e7a3c92-4f6d-4b80-9a15-6e2b0c8d7f34",
      "transactionReference": "INT-20260921-000501",
      "direction": "OUTBOUND",
      "receivedAt": "2026-09-21T07:55:30.118Z",
      "replayed": false
    },
    "decision": {
      "outcome": "PASS",
      "riskScore": 0,
      "evaluatedAt": "2026-09-21T07:55:30.131Z",
      "evaluationMs": 8.402,
      "complete": true,
      "rulesEvaluated": 18,
      "rulesetSnapshot": "9ffa3b395a9799b1a1104ab1b536ed1667b487510829ceefc27593941fafb35e",
      "gradingVersion": 3,
      "triggeredRules": []
    }
  }
}

And this is leg 2 (INBOUND, the receiver), submitted with the same transactionReference but the opposite direction. It is not a replay of leg 1 — direction is part of the idempotency key, so OneView evaluates it as a separate transaction with its own transaction.id:

{
  "status": true,
  "message": "Transaction evaluated",
  "data": {
    "transaction": {
      "id": "9d4b2f61-8a3e-4c05-b716-2f9c0e5a3d87",
      "transactionReference": "INT-20260921-000501",
      "direction": "INBOUND",
      "receivedAt": "2026-09-21T07:55:30.204Z",
      "replayed": false
    },
    "decision": {
      "outcome": "PASS",
      "riskScore": 0,
      "evaluatedAt": "2026-09-21T07:55:30.219Z",
      "evaluationMs": 7.115,
      "complete": true,
      "rulesEvaluated": 18,
      "rulesetSnapshot": "9ffa3b395a9799b1a1104ab1b536ed1667b487510829ceefc27593941fafb35e",
      "gradingVersion": 3,
      "triggeredRules": []
    }
  }
}

Both are 201 responses. See Decision responses for every outcome, and note that a HOLD or BLOCK on one leg has no automatic effect on the other — your system decides how to combine them, as above.

Errors specific to an internal user counterparty

Causefieldcodemessage
No accountName on the counterpartycounterpartyrequiredcounterparty.accountName is required for this counterparty type
counterparty.country is a three-letter or unassigned codecounterparty.countryinvalidcounterparty.country must be an ISO 3166-1 alpha-2 country code
accountHolderType is CUSTOMER and the sender's customerUniqueId is missingaccountHolderrequiredaccountHolder.customerUniqueId is required when accountHolderType is CUSTOMER

A validation failure on leg 1 does not stop you from submitting leg 2 — they are independent requests. See Errors for the complete catalogue.

Idempotency across both legs

Each leg is keyed by transactionReference and direction together, so the two legs of one intra-bank transfer never collide: INT-20260921-000501 + OUTBOUND and INT-20260921-000501 + INBOUND are two separate idempotency keys. If your core banking system retries leg 1 after a timeout, resend it with the identical body and you get back 200 with replayed: true and the original decision — no duplicate debit. The same applies independently to leg 2. Only resending one leg's reference and direction with a changed body triggers 409 IDEMPOTENCY_CONFLICT; retrying with the exact original body is always safe. See Idempotency in Submitting transactions.