Save Payees as Contacts
Save a third-party payee once — with their bank account, mailing address, international account, card, or PPI — and reuse it across payout and collection rails.
A Contact is a saved, reusable third party you pay or collect from — a vendor, contractor, employee, or another business. You store their payout details once (a bank account, mailing address, international account, card, or Passport Payment ID), then reference the saved contact by id on every future transaction instead of re-entering details each time.
A single contact can bundle more than one payment instrument, so you can reach the same payee across different rails. To save the customer's own bank account, use External Accounts instead — Contacts are for third parties.
For the complete field list, jump to the Request reference.
How a Contact Works
- Create the contact with one or more payment instruments —
externalAccount,internationalExternalAccount,card,mailingAddress, or appi. - Verification runs per instrument. Bank-account instruments are OFAC-screened on the holder name (and validated like External Accounts); a
ppiinstrument is verified against an existing Passport account and is usable immediately. - Reference the contact
idwhen you create a payout or collection. One saved contact serves many transactions.
Save a payee once and reuse it. Fewer re-keys means faster initiation, fewer errors, and cleaner reconciliation.
Base Path
Contacts are created under a customer:
| Base path | Description |
|---|---|
/v1/customer/id/{customerId}/contact | Customer-owned contacts (by Passport ID) |
/v1/customer/externalId/{customerExternalId}/contact | Customer-owned contacts (by program-manager externalId) |
Every create, update, verify, and configuration request requires the PromiseMode header (NEVER for an immediate response, ALWAYS to defer processing).
Create Your First Contact
The fastest way to see it work: save a vendor's US bank account so you can pay them by ACH or wire later.
Request
POST /v1/customer/id/{customerId}/contact
PromiseMode: NEVER
{
"externalId": "CON-EXT-0001",
"name": "Acme Supplier",
"contactType": "BUSINESS",
"legalName": "Acme Supplier LLC",
"externalAccount": [
{
"holderName": "Acme Supplier LLC",
"holderType": "CORPORATE",
"accountNumber": "123456789012",
"routingNumber": "021000021",
"wireRoutingNumber": "021000021",
"type": "CHECKING"
}
]
}Response — 201 Created
Create returns 201 Created with an empty body; the new contact's ID is in the url (Location) response header. (With PromiseMode: ALWAYS, you get 202 Accepted and processing continues asynchronously.)
HTTP/1.1 201 Created
url: /v1/customer/id/{customerId}/contact/id/5501
Retrieve the contact to see its instruments and their verification status:
GET /v1/customer/id/{customerId}/contact/id/5501
{
"resourceName": "contact",
"url": "/v1/customer/id/{customerId}/contact/id/5501",
"id": 5501,
"externalId": "CON-EXT-0001",
"name": "Acme Supplier",
"contactType": "BUSINESS",
"legalName": "Acme Supplier LLC",
"externalAccount": [
{
"resourceName": "externalAccount",
"id": 4000364,
"holderName": "Acme Supplier LLC",
"holderType": "CORPORATE",
"accountNumberLast4": "9012",
"routingNumber": "021000021",
"status": "INACTIVE",
"verification": { "ofacStatus": "PENDING_VERIFICATION" }
}
]
}Use
externalIdfor safe retriesFor Treasury, PCE treats the
externalIdin the request body as the idempotency key — there's no separate header. Send a uniqueexternalIdper contact; retrying with the same value creates it only once.
Scenarios
Save a payee by providing one or more instruments, then reference the contact on future transactions. Bank instruments are OFAC-screened; PPI instruments are usable immediately.
sequenceDiagram
participant App as Your application
participant PCE as PCE
participant OFAC as OFAC Screening
App->>PCE: POST /contact (name + instrument)
PCE->>PCE: Field validation
PCE-->>App: 201 Created (id in url header)
alt Bank-account / card instrument
PCE->>OFAC: Screen holder name
OFAC-->>PCE: Cleared / Rejected
PCE-->>App: Webhook — instrument ACTIVE or BLOCKED
else PPI instrument
PCE->>PCE: Verify PPI exists
PCE-->>App: Instrument usable immediately
end
Note over App: Reference contact.id in future transactions
Before you begin (all scenarios)
- Customer exists and is
ACTIVE. - You have the payee's instrument details (bank account, mailing address, PPI, international account, or card).
- Your webhook endpoint is subscribed to contact/external-account events (to track verification).
Scenario 1: Pay a Business by Bank Transfer (ACH / Wire)
Save a third-party US bank account for ACH or domestic wire payouts.
You'll also need: the payee's routing number, account number, account type, and holder type (CONSUMER / CORPORATE).
POST /v1/customer/id/{customerId}/contact
PromiseMode: NEVER
{
"externalId": "contact-vendor-001",
"name": "Acme Supplies",
"contactType": "BUSINESS",
"legalName": "Acme Supplies LLC",
"externalAccount": [
{
"holderName": "Acme Supplies LLC",
"holderType": "CORPORATE",
"accountNumber": "987654321098",
"routingNumber": "021000089",
"wireRoutingNumber": "021000089",
"type": "CHECKING"
}
]
}Scenario 2: Pay an Individual by Mailed Check
Save a mailing address for check payouts.
You'll also need: the recipient's name and a US mailing address.
POST /v1/customer/id/{customerId}/contact
PromiseMode: NEVER
{
"externalId": "contact-check-002",
"name": "Jane Doe",
"contactType": "INDIVIDUAL",
"firstName": "Jane",
"lastName": "Doe",
"mailingAddress": [
{
"name": "Jane Doe",
"addressLine1": "100 Market St",
"city": "San Jose",
"state": "CA",
"zip": "95113"
}
]
}Scenario 3: Pay a Payee with a Passport Account (PPI)
Save an internal payee by their Passport Payment ID for BOOK transfers. PPI instruments skip external validation and are usable immediately.
You'll also need: the payee's PPI. Confirm it first with the Verify PPI API.
POST /v1/customer/id/{customerId}/contact
PromiseMode: NEVER
{
"externalId": "contact-ppi-003",
"name": "Bright Tech",
"contactType": "BUSINESS",
"legalName": "Bright Tech Inc",
"ppi": "brighttech@ppi"
}Scenario 4: Pay a Recipient Abroad (International Wire)
Save an international bank account for cross-border payouts. Field rules follow International External Accounts (valid SWIFT code, accepted currency, holder address).
You'll also need: the SWIFT code, account number/IBAN, accepted currency, and holder address.
POST /v1/customer/id/{customerId}/contact
PromiseMode: NEVER
{
"externalId": "contact-intl-004",
"name": "Globex GmbH",
"contactType": "BUSINESS",
"legalName": "Globex GmbH",
"internationalExternalAccount": [
{
"type": "CHECKING",
"holderName": "Globex GmbH",
"holderType": "CORPORATE",
"accountNumber": "DE89370400440532013000",
"swiftCode": "DEUTDEFF",
"acceptedCurrency": ["EUR"],
"purpose": "Services",
"holderAddress": {
"addressLine1": "Friedrichstrasse 100",
"city": "Berlin",
"country": "DE",
"zip": "10117"
}
}
]
}Scenario 5: Save a Payee's Card
Save a card instrument for the payee. A single-use token generated with the Passport SDK is preferred over raw PAN details.
You'll also need: the recipient's email (required for virtual-card delivery) and either a card token or full card details.
POST /v1/customer/id/{customerId}/contact
PromiseMode: NEVER
{
"externalId": "contact-card-005",
"name": "Sam Lee",
"contactType": "INDIVIDUAL",
"firstName": "Sam",
"lastName": "Lee",
"email": "[email protected]",
"card": [
{ "token": "eyJjYXJkVHlwZS==" }
]
}Verify a PPI
Before saving or paying a PPI-based payee, confirm the Passport Payment ID exists.
POST /v1/customer/id/{customerId}/contact/verifyPpi
PromiseMode: NEVER
{
"ppi": "brighttech@ppi"
}A 204 No Content response means the PPI exists and is valid (no body is returned). An invalid PPI returns 400.
Retrieve, List, and Manage Contacts
- Get a contact:
GET /v1/customer/id/{customerId}/contact/id/{contactId} - List a customer's contacts:
GET /v1/customer/id/{customerId}/contact - Update a contact:
PUT /v1/customer/id/{customerId}/contact/id/{contactId}— returns204(or202withPromiseMode: ALWAYS) - Delete a contact:
DELETE /v1/customer/id/{customerId}/contact/id/{contactId}
Payment Acceptance Configuration (optional)
You can attach payment-acceptance rules to a contact — for example, to constrain a method to an exact amount. Use the Add Contact Configurations API; read them back with Get Contact Configuration.
POST /v1/customer/id/{customerId}/contact/id/{contactId}/configuration
PromiseMode: NEVER
{
"paymentAcceptanceCriteria": [
{
"method": ["MONEYGRAM"],
"matchType": "EXACT",
"amount": "1000.00"
}
]
}A successful call returns 204 No Content.
Request Reference
Contact core fields
| Parameter | Required | Type | Description |
|---|---|---|---|
name | ✓ | String | Display label for the contact |
contactType | Conditional | String | INDIVIDUAL or BUSINESS |
firstName | Conditional | String | For INDIVIDUAL contacts |
lastName | Conditional | String | For INDIVIDUAL contacts |
legalName | Conditional | String | For BUSINESS contacts |
email | Conditional | String | Required for a virtual-card (card) payout. Format [email protected] |
ppi | Conditional | String | Passport Payment ID of an internal payee |
externalId | Recommended | String | Your reference ID; used as the idempotency key |
Provide at least one payment instrument below (a contact can hold several).
Instrument: externalAccount[] (domestic bank account)
externalAccount[] (domestic bank account)| Parameter | Required | Description |
|---|---|---|
holderName | ✓ | Account holder name as on bank records |
holderType | ✓ | CONSUMER or CORPORATE |
accountNumber | ✓ | Bank account number |
routingNumber | ✓ | 9-digit ACH routing number |
type | ✓ | SAVINGS or CHECKING |
wireRoutingNumber | Optional | 9-digit routing number for wires, if different |
holderPhone | Optional | Holder phone number |
holderAddress | Optional | Holder address (addressLine1, city, state, zip) |
Instrument: internationalExternalAccount[]
internationalExternalAccount[]Field rules match International External Accounts: type, holderName, holderType, accountNumber, swiftCode, acceptedCurrency (array), holderAddress, and conditional internationalRoutingCode / purpose.
Instrument: mailingAddress[] (check)
mailingAddress[] (check)| Parameter | Required | Description |
|---|---|---|
addressLine1 | ✓ | Street address (4–40 chars) |
city | ✓ | City |
state | ✓ | Two-character US state code |
zip | ✓ | 5- or 9-digit ZIP |
name | Optional | Name of the address owner |
phone | Optional | Phone number |
Instrument: card
card| Parameter | Required | Description |
|---|---|---|
token | Preferred | Single-use token generated with the Passport SDK |
cardNumber | If no token | 13–17 digit card number |
expiryMonth / expiryYear | If no token | mm / yyyy |
cvv | If no token | Card CVV |
brand | If no token | VISA, MASTERCARD, DISCOVER, AMERICAN_EXPRESS |
holderName | If no token | Cardholder name |
billingAddress / avs | Optional | Billing address and AVS details |
Verification & Status
- Bank-account and card instruments are OFAC-screened on the holder name and follow the same lifecycle as External Accounts: they start
INACTIVE, becomeACTIVEonce screening and validation clear, and areBLOCKEDif OFAC/validation fails. Each instrument in the retrieve response carries its ownstatusandverification.ofacStatus. - PPI instruments are verified against an existing Passport account and are usable immediately.
- Track verification with contact / external-account webhooks, or by retrieving the contact and checking each instrument's
status.
Validation
When you submit the request, we validate:
| Check | Error if failed |
|---|---|
name present | 400 — Required field |
| At least one valid instrument provided | 400 — Missing instrument |
Bank instrument has accountNumber, routingNumber, holderName, holderType, type | 400 — Missing field |
| SWIFT code valid (international) | 400 — Invalid routing code |
| PPI exists (PPI instrument) | 400 — Invalid PPI |
| Contact exists and not deleted (on retrieve/update) | 404 — Not found |
| Contact belongs to the path customer | 403 — Owner mismatch |
Best Practices
| Practice | Description |
|---|---|
Set a unique externalId | Used as the idempotency key — retries never create duplicates |
| Verify a PPI before saving | Confirms the internal payee exists |
Use a card token, not raw PAN | SDK-generated single-use tokens avoid handling card data |
| Reuse contacts for repeat payments | Faster initiation, fewer errors, cleaner reconciliation |
Store the contact id | Required to pay, retrieve, update, or delete |
| Monitor instrument status | A payout can only use an ACTIVE bank instrument |
Related
Pay a saved contact's bank account over the ACH network.
Send funds by physical check to a contact's mailing address.
Send funds to a contact's bank account via domestic wire.
Send USD and non-USD payouts to a contact's international account.
Move funds to a contact's Passport account (PPI).
Save the customer's own bank accounts, not third-party contacts.
Updated about 15 hours ago