# Get Account by ID Source: https://docs.mnai.com/api-reference/accounts/get-account-by-id /products/meridian-accounts/api-reference/meridian-accounts.json get /v1/accounts/{accountId} Returns the account details for the authenticated Meridian account. # List Accounts Source: https://docs.mnai.com/api-reference/accounts/list-accounts /products/meridian-accounts/api-reference/meridian-accounts.json get /v1/accounts Returns the accounts associated with the authenticated Meridian account. # Create a Meridian user for a partner Source: https://docs.mnai.com/api-reference/auth/create-a-meridian-user-for-a-partner /products/meridian-accounts/api-reference/meridian-accounts.json post /v1/auth/users Creates a person or business user for the calling partner on the supplied program. Only MULTI_USER integrations may call this endpoint; SINGLE_USER keys are rejected. # Issue a Meridian Accounts access token Source: https://docs.mnai.com/api-reference/auth/issue-a-meridian-accounts-access-token /products/meridian-accounts/api-reference/meridian-accounts.json post /v1/auth/token Mints a JWT for a Meridian user. For `client_credentials` grants, the user is identified by the `X-Meridian-User-Id` header: it must be provided for MULTI_USER integrations and omitted for SINGLE_USER integrations (the single configured user is used). For `refresh_token` grants, the user is derived from the supplied refresh token. # Activate a pending enrollment Source: https://docs.mnai.com/api-reference/enrollment/activate-a-pending-enrollment /products/meridian-accounts/api-reference/meridian-accounts.json post /v1/enrollment/activate Creates or resumes the enrollment for the authenticated Meridian account and returns the current enrollment state. # Get current enrollment status Source: https://docs.mnai.com/api-reference/enrollment/get-current-enrollment-status /products/meridian-accounts/api-reference/meridian-accounts.json get /v1/enrollment Returns the enrollment for the authenticated Meridian account, creating one on the first call if none exists yet. # Create or update Information Request requirement entry data Source: https://docs.mnai.com/api-reference/information-requests/create-or-update-information-request-requirement-entry-data /products/meridian-accounts/api-reference/meridian-accounts.json post /v1/information-requests/{informationRequestId}/data Writes structured data for one or more requirement entries. # Get an Information Request by ID Source: https://docs.mnai.com/api-reference/information-requests/get-an-information-request-by-id /products/meridian-accounts/api-reference/meridian-accounts.json get /v1/information-requests/{informationRequestId} Returns the Information Request details for the authenticated Meridian account. # List all Information Requests Source: https://docs.mnai.com/api-reference/information-requests/list-all-information-requests /products/meridian-accounts/api-reference/meridian-accounts.json get /v1/information-requests Returns the Information Requests associated with the authenticated Meridian account. # Submit an Information Request for review Source: https://docs.mnai.com/api-reference/information-requests/submit-an-information-request-for-review /products/meridian-accounts/api-reference/meridian-accounts.json post /v1/information-requests/{informationRequestId}/submit Submits the Information Request for the authenticated Meridian account. # Upload and attach an Information Request document Source: https://docs.mnai.com/api-reference/information-requests/upload-and-attach-an-information-request-document /products/meridian-accounts/api-reference/meridian-accounts.json post /v1/information-requests/{informationRequestId}/documents Uploads a document for a requirement entry and stores it. # Submit a payout request Source: https://docs.mnai.com/api-reference/payouts/submit-a-payout-request /products/payout-origination/api-reference/payout-origination.json post /v1/payout-origination/{format} Accepts a Base64-encoded payout request in the format specified by the `format` path parameter. Supported values: - `pacs008` for ISO 20022 `pacs.008.*` XML messages - `json` for a JSON payload where `entityId`, `beneficiaryId`, `amount`, `currency`, and `reference` are all required Authentication uses Meridian HMAC headers: - X-Meridian-Api-Key - X-Meridian-Timestamp - X-Meridian-Signature Use Idempotency-Key as a separate header for duplicate protection. The endpoint returns 202 Accepted once the encoded payload has been safely accepted for processing. Webhooks will be sent for each status change until a finality status. The transaction APIs may be used to list resulting transactions and retrieve the latest status on demand. # Send a Mock Deposit Source: https://docs.mnai.com/api-reference/sandbox/send-a-mock-deposit /products/meridian-accounts/api-reference/meridian-accounts.json post /v1/accounts/{accountId}/simulate-deposit Simulates an inbound deposit into a balance account through a given deposit instruction, so you can exercise downstream processing — balances, transactions, and webhooks — without moving real money. This is fire-and-acknowledge: it responds `202` with an empty body and settles out of band, so observe the result by polling the account balance or transactions. > **Sandbox only.** This endpoint exists exclusively in the sandbox environment for testing. Simulating a deposit is not possible in production. # Commit a transaction intent Source: https://docs.mnai.com/api-reference/transaction-intents/commit-a-transaction-intent /products/meridian-accounts/api-reference/meridian-accounts.json post /v1/transaction-intents/{transactionIntentId}/commit Commits a created transaction intent. Commit is idempotent and creates at most one underlying Meridian transaction. # Create a transaction intent Source: https://docs.mnai.com/api-reference/transaction-intents/create-a-transaction-intent /products/meridian-accounts/api-reference/meridian-accounts.json post /v1/transaction-intents Creates a transaction-shaped preview from debit and credit payment instruments. No funds movement occurs until the intent is committed. # Get a payout transaction Source: https://docs.mnai.com/api-reference/transactions/get-a-payout-transaction /products/payout-origination/api-reference/payout-origination.json get /v1/transactions/{id} Returns a transaction with its latest processing status by Meridian transaction ID. Authentication uses Meridian HMAC headers: - X-Meridian-Api-Key - X-Meridian-Timestamp - X-Meridian-Signature # Get Transaction by ID Source: https://docs.mnai.com/api-reference/transactions/get-transaction-by-id /products/meridian-accounts/api-reference/meridian-accounts.json get /v1/transactions/{transactionId} Returns the transaction details for the authenticated Meridian account. # List payout transactions Source: https://docs.mnai.com/api-reference/transactions/list-payout-transactions /products/payout-origination/api-reference/payout-origination.json get /v1/transactions Returns transactions with their latest processing status. You can optionally filter the results by the Meridian `messageId` returned from the message submission endpoint. Authentication uses Meridian HMAC headers: - X-Meridian-Api-Key - X-Meridian-Timestamp - X-Meridian-Signature # List Transactions Source: https://docs.mnai.com/api-reference/transactions/list-transactions /products/meridian-accounts/api-reference/meridian-accounts.json get /v1/transactions Returns the transactions associated with the authenticated Meridian account. # Postman collection Source: https://docs.mnai.com/postman-collection Import the official Meridian Postman collection. HMAC signing is handled for you — set your credentials and start making requests. Meridian publishes a Postman collection that covers Meridian Accounts, Payout Origination, and Retail Virtual Accounts. Signing is handled for you, so once your credentials are set you can call any endpoint immediately. ## Import In Postman, select **Import** in the top-left and select the **Link** tab. Paste this URL and select **Continue**, then **Import**: ``` https://download.mnai.com/postman/meridian.postman_collection.json ``` The collection appears in your sidebar as **Meridian API**, with one folder per product. Create a new **Environment** (left sidebar → Environments → **+**) called something like `Meridian sandbox`. Add the variables listed below and fill in your credentials there. Make the environment active using the dropdown in the top-right. Storing credentials in an Environment (rather than on the collection) means re-importing later won't wipe them. Prefer a file? Download [`meridian.postman_collection.json`](https://download.mnai.com/postman/meridian.postman_collection.json) and use **Import → File** instead. ## Variables | Variable | Description | | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | | `baseUrl` | API base URL. Defaults to the sandbox host; override per environment when you're ready for production. | | `meridianApiKey` | Your Meridian API key. | | `meridianApiSecret` | Your Meridian API secret. Used only to compute the request signature — it is never sent on the wire. | | `meridianProgramId` | Your Meridian Program ID. Required for Meridian Accounts and Payout Origination. | | `meridianUserId` | The Meridian user the call targets. Required for Meridian Accounts MULTI\_USER integrations; leave blank for SINGLE\_USER. | | `meridianAccessToken` | Populated automatically after a successful call to `POST /v1/auth/token`. Use `{{meridianAccessToken}}` in any request that needs a bearer token. | | `meridianRefreshToken` | Populated automatically after a successful call to `POST /v1/auth/token`. Picked up automatically by the Refresh Token request. | Sandbox and production credentials are different. Do not reuse them across environments. ## Troubleshooting If a request fails with `401 Unauthorized`, the most common causes are: * An incorrect or missing `meridianApiSecret` * Clock skew greater than 60 seconds between your machine and Meridian * Using sandbox credentials against production (or vice versa) * A `meridianProgramId` mismatch for the targeted product ## Updates The collection is updated automatically when the API changes. To pull the latest version, re-run the import from the same URL — Postman will offer to **Replace** the existing collection. Re-importing replaces the collection and resets any values you stored directly on it. Keep your credentials in a Postman Environment to avoid having to re-enter them. # Credit API Source: https://docs.mnai.com/products/bank-transfers/credit-api Push funds from Meridian's sponsored account to a customer's account at the home bank. The Credit API is the bank partner's endpoint for receiving and processing payout instructions from Meridian. Meridian calls it each time a product event requires funds to be pushed to a customer — for example, when a customer initiates a withdrawal from a Retail Virtual Account. Meridian's Implementation team integrates with the bank partner's Credit API during onboarding. The exact specification is partner-dependent, and Meridian adapts to what the bank provides. If the bank partner does not have an existing Credit API, Meridian can provide consultation to help scope the integration and define requirements before development begins. See [Reference implementation](#reference-implementation) below for a recommended starting point. ## How it works Meridian sends a credit instruction to the bank partner's Credit API with the transfer amount and relevant customer details. The bank executes an intra-bank transfer from Meridian's sponsored account to the customer's account at the home bank. The bank then returns a confirmation, and Meridian uses it to update the transaction status. ## Integration requirements Meridian supports multiple authentication methods and aligns with the bank partner's Credit API requirements during onboarding. Meridian formats the request body according to the bank partner's specification, including any required encryption or signing. Meridian passes transaction details and sender/recipient KYC data with every credit instruction. Meridian confirms exact fields during onboarding. If the bank exposes a validation endpoint, Meridian can confirm recipient account eligibility before executing a credit. Meridian submits the credit instruction to the bank partner's API to initiate the actual money movement from Meridian's sponsored account to the customer's account. Meridian requires a mechanism to retrieve final transaction status after you submit a credit instruction. ## Planning considerations If you are building a Credit API for the first time, account for the following before development begins. **Idempotency** — Meridian includes a unique transaction ID with every credit instruction. The Credit API must use this ID to detect and safely reject duplicate requests, ensuring a customer is never credited twice for the same transaction. **Response model** — `commitCredit` should return a final result synchronously. This allows Meridian to update the transaction status immediately and deliver the best customer experience. If the bank's core system cannot support a synchronous response, an asynchronous model is possible — Meridian will poll `getStatus` to confirm the outcome. Treat this as a fallback and align it with Meridian during scoping. **Sandbox environment** — A non-production environment is required before go-live. Meridian will use it to validate the integration end-to-end before any real money movement occurs. ## Reference implementation If the bank partner does not have an existing Credit API, the following endpoints provide a recommended baseline. Meridian can use this as a starting point during the scoping and requirements phase. | Endpoint | Purpose | | :--------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `createCredit` | Establishes the transaction and handles any required setup before money movement begins. | | `validateCredit` | Confirms recipient account eligibility before a credit is attempted. | | `commitCredit` | Initiates the actual money movement from Meridian's sponsored account to the customer's account. | | `getStatus` | Fetches the current transaction status to confirm success or failure. Must support detailed error codes and align on retry strategy with Meridian during onboarding. | # Debit API Source: https://docs.mnai.com/products/bank-transfers/debit-api Pull funds from a customer's account into Meridian's sponsored account at the home bank. The Debit API is the bank partner's endpoint for receiving and processing pull instructions from Meridian. Meridian calls it each time a product event requires funds to be pulled from a customer — for example, when a customer converts local currency between their home bank accounts. Meridian's Implementation team integrates with the bank partner's Debit API during onboarding. The exact specification is partner-dependent, and Meridian adapts to what the bank provides. If the bank partner does not have an existing Debit API, Meridian can provide consultation to help scope the integration and define requirements before development begins. See [Reference implementation](#reference-implementation) below for a recommended starting point. ## How it works When a customer converts local currency between their home bank accounts, Meridian sends a debit instruction to the bank partner's Debit API. The instruction includes the transfer amount and relevant customer details. The bank executes an intra-bank transfer from the customer's local currency account into Meridian's sponsored account at the home bank. The bank then returns a confirmation, and Meridian uses it to update the transaction status. ## Integration requirements Meridian supports multiple authentication methods and aligns with the bank partner's Debit API requirements during onboarding. Some banks require a one-time approval before funds can be pulled from a customer's account. If required, Meridian will request authorization before proceeding to commit the debit. Meridian formats the request body according to the bank partner's specification, including any required encryption or signing. Meridian passes transaction details and sender/recipient KYC data with every debit instruction. Meridian confirms exact fields during onboarding. If the bank exposes a validation endpoint, Meridian can confirm the source account is eligible and has sufficient funds before executing a debit. Meridian submits the debit instruction to the bank partner's API to initiate the actual money movement from the customer's account into Meridian's sponsored account. Meridian requires a mechanism to retrieve final transaction status after you submit a debit instruction. ## Planning considerations If you are building a Debit API for the first time, account for the following before development begins. **Idempotency** — Meridian includes a unique transaction ID with every debit instruction. The Debit API must use this ID to detect and safely reject duplicate requests, ensuring a customer is never debited twice for the same transaction. **Response model** — `commitDebit` should return a final result synchronously. This allows Meridian to update the transaction status immediately and deliver the best customer experience. If the bank's core system cannot support a synchronous response, an asynchronous model is possible — Meridian will poll `getStatus` to confirm the outcome. Treat this as a fallback and align it with Meridian during scoping. **Sandbox environment** — A non-production environment is required before go-live. Meridian will use it to validate the integration end-to-end before any real money movement occurs. **Insufficient funds handling** — Unlike a credit, a debit can fail because the source account does not have enough funds. The bank must provide clear error codes for this scenario, and you should define a retry strategy with Meridian during onboarding. ## Reference implementation If the bank partner does not have an existing Debit API, the following endpoints provide a recommended baseline. Meridian can use this as a starting point during the scoping and requirements phase. | Endpoint | Purpose | | :--------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `authorizeDebit` | *(Optional)* Requests one-time approval from the bank before funds can be pulled from the customer's account. | | `createDebit` | Establishes the transaction and handles any required setup before money movement begins. | | `validateDebit` | Confirms the source account is eligible and has sufficient funds. | | `commitDebit` | Initiates the actual money movement from the customer's account into Meridian's sponsored account. | | `getStatus` | Fetches the current transaction status to confirm success or failure. Must support detailed error codes and align on retry strategy with Meridian during onboarding. | # Overview Source: https://docs.mnai.com/products/bank-transfers/overview How Meridian instructs bank partners to move money in real time. Instant bank transfers work by instructing the bank to perform an intra-bank transfer between Meridian's sponsored account and the customer's account at the home bank. Because these transfers settle within the bank's own systems, a single bank partner integration can serve all Meridian products — Retail Virtual Accounts, Meridian Accounts for Business, and Payment Collections. ## Meridian's integration approach Meridian's Implementation team owns the integration with each bank partner's Credit or Debit API. Because API specifications are entirely partner-dependent, Meridian adapts to the API specification the bank partner provides. In many cases, the Credit and Debit API may be the same API at the bank. That single intra-bank account-to-account book transfer API supports both push and pull directions. If a bank partner does not have an existing API, Meridian can provide consultation to help scope the integration and define requirements before development begins. ## Integration types Push funds from Meridian's sponsored account to an account holder's account. Pull funds from an account holder's account into Meridian's sponsored account. # Overview Source: https://docs.mnai.com/products/meridian-accounts/api-reference/index Learn how to authenticate and use the Meridian Accounts API. ## Base URLs | Environment | URL | Purpose | | :---------- | :--------------------------------------- | :----------- | | Sandbox | `https://sandbox-api.va.meridianpay.com` | Testing | | Production | `https://api.va.mnai.com` | Live traffic | Meridian provides your Sandbox environment during onboarding. Validate your integration there before using production. ## Authentication The Meridian Accounts API supports two authentication layers: * Partner HMAC authentication when your backend proves partner identity with `X-Meridian-*` headers and an HMAC SHA-256 signature * JWT bearer authentication when your client or backend sends `Authorization: Bearer {token}` after a token has already been minted for a user In a typical integration, your backend uses partner HMAC authentication to call `POST /v1/auth/token`, then later user-scoped requests use the returned JWT. For the conceptual auth guides, see: * [Authentication overview](/products/meridian-accounts/guides/authentication-overview) * [Server-to-server authentication with HMAC](/products/meridian-accounts/guides/server-to-server-authentication-with-hmac) * [Client-server authentication with JWT](/products/meridian-accounts/guides/client-server-authentication-with-jwt) # Authentication overview Source: https://docs.mnai.com/products/meridian-accounts/guides/authentication-overview Learn when Meridian Accounts uses partner HMAC authentication versus Meridian JWT bearer authentication. Meridian Accounts uses two authentication layers that serve different purposes: * **Partner HMAC authentication** proves that your backend is an entitled Meridian integrator * **JWT bearer authentication** carries Meridian user context after a token has already been minted These are not competing end-user options. In a typical integration, your backend first authenticates with partner credentials and HMAC, then receives a Meridian access token, and later uses that JWT on behalf of the target user. ## Which authentication method to use Use this decision tree for each request: * If your backend is calling Meridian directly with your partner API Key and Secret, use [server-to-server authentication with HMAC](/products/meridian-accounts/guides/server-to-server-authentication-with-hmac) * If you already have a Meridian access token and are making a user-scoped API call, use [client-server authentication with JWT](/products/meridian-accounts/guides/client-server-authentication-with-jwt) ## Authentication matrix | Scenario | Caller | Credential type | Required auth fields | Typical endpoints | | :------------------------------ | :-------------------- | :-------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------- | | Backend proves partner identity | Your server | Meridian API Key + HMAC signature | `X-Meridian-Api-Key`, `X-Meridian-Program-Id`, `X-Meridian-Timestamp`, `X-Meridian-Signature`, and `X-Meridian-User-Id` for `MULTI_USER` | `POST /v1/auth/token`, partner-key endpoints supported by the API | | User-scoped API access | Your client or server | Meridian bearer token | `Authorization: Bearer {token}` | `GET /v1/enrollment`, `GET /v1/virtual-accounts`, `GET /v1/transactions` | ## How the flow fits together 1. Your backend sends a partner-authenticated request signed with HMAC. 2. Meridian issues a JWT access token for the target user. 3. Your client or backend uses `Authorization: Bearer {token}` on later API requests. Keep your Meridian API Secret on the server only. Do not try to generate HMAC signatures from a browser or mobile client. ## Related guides * [Choose your integration model](/products/meridian-accounts/guides/choose-your-integration-model) * [Server-to-server authentication with HMAC](/products/meridian-accounts/guides/server-to-server-authentication-with-hmac) * [Client-server authentication with JWT](/products/meridian-accounts/guides/client-server-authentication-with-jwt) # Choose your integration model Source: https://docs.mnai.com/products/meridian-accounts/guides/choose-your-integration-model Understand how SINGLE_USER and MULTI_USER integrations differ and how that choice affects authentication and onboarding. Before you implement authentication or onboarding, decide whether your Meridian Accounts integration is configured as `SINGLE_USER` or `MULTI_USER`. This is an integration configuration choice, not an authentication method. It determines how your partner credentials relate to Meridian users and whether `X-Meridian-User-Id` is required on partner-authenticated requests. ## Integration types at a glance | Integration type | Meridian user represents | `X-Meridian-User-Id` on partner-key calls | `POST /v1/auth/users` | Credential scope | | :--------------- | :------------------------------------------------------------- | :---------------------------------------- | :-------------------- | :-------------------------------------------------------- | | `SINGLE_USER` | One fixed Meridian user configured for the partner integration | Must be omitted | Not allowed | One partner credential set serves one fixed Meridian user | | `MULTI_USER` | One Meridian user per customer or business you manage | Required | Allowed | One partner credential set can act on many Meridian users | ## Exact request rules The Meridian Accounts API applies these rules to partner-key requests: * `MULTI_USER`: send `X-Meridian-User-Id` to identify which Meridian user the request targets * `SINGLE_USER`: omit `X-Meridian-User-Id`; Meridian uses the single configured user for that partner key These rules apply when your backend is authenticating directly with Meridian by using partner credentials and HMAC. ## How this affects token creation When you call `POST /v1/auth/token`, the integration type changes how Meridian determines the user: * `SINGLE_USER`: Meridian derives the user from the partner key configuration, so `X-Meridian-User-Id` must not be sent * `MULTI_USER`: your request must identify the target user with `X-Meridian-User-Id` If you are managing many customers, `MULTI_USER` also allows you to create or look up Meridian users and then mint access tokens for the correct user in the selected `programId`. ## How this affects onboarding `SINGLE_USER` and `MULTI_USER` do not change the program enrollment lifecycle, but they do change how you identify the user entering that lifecycle: * `SINGLE_USER`: onboarding operates on the one Meridian user already tied to the partner configuration * `MULTI_USER`: onboarding operates on the specific Meridian user identified by `X-Meridian-User-Id` For `MULTI_USER` integrations, you should keep your own mapping between customer records, `externalId`, and Meridian `userId`. Persist the returned `userId` when the user is first created so later token requests can reuse that mapping. ## Recommended reading order After choosing the integration model, continue with: * [Authentication overview](/products/meridian-accounts/guides/authentication-overview) * [Server-to-server authentication with HMAC](/products/meridian-accounts/guides/server-to-server-authentication-with-hmac) * [Client-server authentication with JWT](/products/meridian-accounts/guides/client-server-authentication-with-jwt) # Client-server authentication with JWT Source: https://docs.mnai.com/products/meridian-accounts/guides/client-server-authentication-with-jwt Use the Meridian access token as a bearer token after your backend has minted it with partner HMAC authentication. The Meridian JWT is the access token returned by `POST /v1/auth/token`. After your backend mints that token, your client or backend can send it on later Meridian Accounts requests by using: ```http theme={null} Authorization: Bearer {token} ``` ## Intended flow JWT authentication is the second step in the Meridian Accounts authentication model: 1. Your backend authenticates to Meridian with partner HMAC credentials. 2. Meridian returns an access token for the target user. 3. Your client or backend uses that JWT on later user-scoped API requests. Use this flow when you need to call Meridian on behalf of a specific user after you have already established that user context. ## What JWT-authenticated requests need JWT-authenticated requests use the bearer token in the `Authorization` header. They do not need partner authentication headers such as: * `X-Meridian-Api-Key` * `X-Meridian-Program-Id` * `X-Meridian-Timestamp` * `X-Meridian-Signature` * `X-Meridian-User-Id` The access token already carries the user context for the request. ## Typical JWT use cases Use JWT bearer authentication for user-scoped operations such as: * checking the current enrollment with `GET /v1/enrollment` * reading information requests and submitting onboarding data * listing balances * reading transaction history For frontend-facing account experiences, JWT bearer authentication is the recommended model because it avoids exposing partner HMAC credentials to the client. ## Refreshing tokens When the current access token expires, your backend can exchange a previously issued refresh token by calling `POST /v1/auth/token` with the `refresh_token` grant. Meridian derives the user from the refresh token in that flow, so the request does not depend on a new client-side HMAC signature. Keep the refresh-token exchange on the backend together with your other partner-authenticated token management logic. ## Security notes * never mint Meridian JWTs directly from a browser or mobile client * keep the Meridian API Secret on the backend only * issue or refresh access tokens from trusted server-side code, then pass the resulting bearer token to the component that needs to call Meridian ## Related guides * [Authentication overview](/products/meridian-accounts/guides/authentication-overview) * [Server-to-server authentication with HMAC](/products/meridian-accounts/guides/server-to-server-authentication-with-hmac) * [Displaying account information](/products/meridian-accounts/guides/displaying-account-information) # Displaying account information Source: https://docs.mnai.com/products/meridian-accounts/guides/displaying-account-information Learn how to use Meridian Accounts to display deposit instructions and account activity in your UI. Once a customer's enrollment is `ACTIVE`, your integration can begin showing Meridian balance information in your UI. ## Authentication for account reads These account-read operations can be performed with a Meridian JWT and, where the API supports direct partner authentication, with HMAC-signed partner credentials. For client-facing account views, prefer JWT bearer authentication so your frontend uses `Authorization: Bearer {token}` instead of partner credentials. Review [Client-server authentication with JWT](/products/meridian-accounts/guides/client-server-authentication-with-jwt) before implementing frontend-facing account reads. In Meridian Accounts, the balance is the customer-facing object to display. It represents how your customer receives local payments. Its schema is designed to present deposit instructions that can be shared with senders. ## How to display account information Use the balance and transaction APIs as read models for your UI. At a high level, the display flow is: Call `GET /v1/balances` to retrieve the balances available for the customer in the current program context. Call `GET /v1/balances/{balanceId}` to retrieve the full balance detail and render the deposit instructions your customer can share with a sender. Call `GET /v1/transactions` to populate the customer's account activity feed. Call `GET /v1/transactions/{transactionId}` when your UI needs the full detail for a selected transaction. ## What the balance represents The balance is meant for display in your UI. Use it to show the customer where to send funds, which deposit instructions to share, and what funds are available versus actual. When a sender uses those deposit instructions, the balance receives the incoming funds. Depending on how that balance is configured, the deposited funds may either remain on the balance or trigger a follow-on sweep to the configured credit instrument. ## How to interpret transactions The transaction APIs are designed for display purposes. Treat them as read-only account activity, not as endpoints for creating, editing, or reversing movement. Any funds received into a balance result in a `deposit` transaction. If the balance is configured with a `SWEEP` routing policy, Meridian may also initiate a subsequent `withdrawal` transaction for that same flow. This means your customer may see both: * an inbound `deposit` into the balance * a follow-on `withdrawal` that moves funds to the configured credit instrument ## Using `sourceType` to explain what happened The `sourceType` on a transaction helps explain what caused that transaction. For now, your integration should interpret: * `DEPOSIT_INSTRUCTION` as activity directly associated with a balance deposit instruction * `ROUTING_POLICY` as follow-on movement that Meridian initiated automatically from the balance routing policy Use `type` together with `sourceType` to help the customer understand whether they are looking at the incoming deposit or at a subsequent automatic withdrawal. ## Recommended UI behavior Your integration should: * use the balance responses to render shareable deposit instructions and balance amounts * present transactions as a ledger or activity feed * treat transaction responses as read-only history * use transaction `type` and `sourceType` to explain why a movement appears in the account timeline ## Next steps After implementing this flow, review: * [Onboarding your customer](/products/meridian-accounts/guides/onboarding-your-customer) * [Client-server authentication with JWT](/products/meridian-accounts/guides/client-server-authentication-with-jwt) * [API reference overview](/products/meridian-accounts/api-reference) * [Webhooks overview](/products/meridian-accounts/webhooks) * [Transaction webhooks](/products/meridian-accounts/webhooks/webhooks-transaction) * [Account webhooks](/products/meridian-accounts/webhooks/webhooks-account) # Getting started Source: https://docs.mnai.com/products/meridian-accounts/guides/getting-started Set up your Meridian Accounts Sandbox credentials, understand how Meridian identifies your customers, and prepare for production access. Meridian starts your integration in Sandbox. You will receive a Sandbox API Key, Sandbox Secret, and Program ID to build and test against the Meridian Accounts API. ## Before you integrate You need these Sandbox credentials from Meridian: * `API Key` * `Secret` * `Program ID` Use the Program ID when authenticating with the API. ## Integration path Meridian provides your Sandbox API Key, Sandbox Secret, and Program ID. Use those credentials to integrate your application with the Meridian Accounts API and webhooks in the Sandbox environment. Meridian reviews and certifies your integration before enabling production access. After certification, Meridian provides your production API Key and Secret. ## How Meridian identifies your customers Your system keeps its own customer record. In Meridian, each of your customers maps to a single Meridian user. You supply `externalId` as your unique identifier for that customer. Use `externalId` whenever you need to refer to that customer from your system. Meridian returns `userId` as the Meridian identifier for that same user. You should store Meridian `userId` in your system after you first receive it. A Meridian user can belong to one or more programs. `programId` identifies the program context used when authenticating with the API and when Meridian sends webhook metadata. | Identifier | Issued by | Purpose | | :----------- | :--------- | :--------------------------------------------------------------------- | | `externalId` | Integrator | Identifies your customer and enforces uniqueness in your system | | `userId` | Meridian | Identifies the Meridian user that represents that customer | | `programId` | Meridian | Identifies the program context for authentication and webhook metadata | ## Choose your integration and authentication model first Before you implement token creation, decide: * whether your Meridian Accounts integration is `SINGLE_USER` or `MULTI_USER` * whether you are making a given request with partner HMAC authentication or a Meridian JWT bearer token These decisions affect whether `X-Meridian-User-Id` is required, how your backend mints tokens, and which authentication headers later requests should send. Review these guides before building the auth flow: * [Choose your integration model](/products/meridian-accounts/guides/choose-your-integration-model) * [Authentication overview](/products/meridian-accounts/guides/authentication-overview) * [Server-to-server authentication with HMAC](/products/meridian-accounts/guides/server-to-server-authentication-with-hmac) * [Client-server authentication with JWT](/products/meridian-accounts/guides/client-server-authentication-with-jwt) ## Where you first see this For a first-time `MULTI_USER` customer flow, create the Meridian user first with `POST /v1/auth/users`, then mint a token with `POST /v1/auth/token`. The create-user response is typically the first place you will see Meridian `userId` for that customer. Store that `userId` in your system so you can track the Meridian user alongside your own customer record and reuse it on later token requests. ## Handling duplicate users Meridian enforces uniqueness for `externalId` at the Meridian-user level. If you send `POST /v1/auth/users` with an `externalId` that has already been used, the request fails instead of creating another Meridian user for that customer. Before creating a user, determine whether the customer already has a Meridian `userId` in your system. If they do, use that `userId` to create the token, even if the customer still needs enrollment in the selected program. Store the returned `userId` when you first create the Meridian user, and rely on your own customer-to-user mapping on later token requests. This matters because Meridian users can belong to more than one program. A customer may already have a Meridian user record but still need enrollment in the program you selected for the current flow. ## Next steps After you receive your Sandbox credentials, review: * [Choose your integration model](/products/meridian-accounts/guides/choose-your-integration-model) * [Authentication overview](/products/meridian-accounts/guides/authentication-overview) * [API reference overview](/products/meridian-accounts/api-reference) * [Webhooks overview](/products/meridian-accounts/webhooks) # Information we collect Source: https://docs.mnai.com/products/meridian-accounts/guides/kyb-information The business, ownership, and document information collected when opening a Meridian Account for a business (KYB). When a business opens a Meridian Account, Meridian collects the information below for Know Your Business (KYB) verification. All information is subject to independent verification, and Meridian may request additional documentation at any time. **This list can change.** Onboarding collects KYB requirements dynamically, and Meridian may adjust or add to them as compliance requirements evolve. This page may not always reflect the exact current set — always treat the onboarding flow and the account opening form provided by Meridian as the authoritative source. ## Business information | Field | Field name | Type | Required | Notes | | :----------------------------- | :------------------------------ | :------------- | :------- | :------------------------------------------------------------------------------ | | Legal name of entity | `legalName` | String(150) | Yes | Exactly as it appears on formation documents | | Trade name / DBA | `displayName` | String(150) | No | | | Tax ID number | `taxNumber` | String(50) | Yes | | | Business registration number | `registrationNumber` | String(50) | Yes | | | Business email | `email` | String(150) | Yes | | | Date of formation | `incorporationDate` | Date | Yes | Optional if retrievable from the proof of incorporation | | Description of business | `description` | String(700) | Yes | | | Business vertical | `businessVertical` | String (enum) | Yes | Industry category classification — see Business vertical values below | | Website URL | `websiteUrl` | String | Yes | | | Industry | `industry` | String(150) | Yes | | | Business type | `businessType` | String (enum) | Yes | Legal entity type — see Business type values below | | Proof of business registration | `business_registration_proof` | Document (PDF) | Yes | | | Proof of business address | `business_address_proof` | Document (PDF) | Yes | | | Proof of business identity | `business_identity_proof` | Document (PDF) | Yes | | | Ownership chart | `business_article_of_ownership` | Document (PDF) | Yes | Showing all direct and indirect owners, including percentage held at each level | * `BENEFIT_CORPORATION` * `CLOSELY_HELD_CORPORATION` * `COOPERATIVE` * `CORPORATION` * `GENERAL_PARTNERSHIP` * `LIMITED_LIABILITY_COMPANY` * `LIMITED_PARTNERSHIP` * `NONPROFIT_CORPORATION` * `NON_US_ENTITY` * `SMALL_BUSINESS_CORPORATION` * `SOLE_PROPRIETORSHIP` * `ECOMMERCE_AUTOMOBILES_AUTOMOBILE_PARTS_TOOLS_ACCESSORIES_AND_EQUIPMENT` * `ECOMMERCE_B2B_OTHERS_MEDICAL_DENTAL_OPHTHALMIC_AND_HOSPITAL_EQUIPMENT_AND_SUPPLIES_AND_PPE` * `ECOMMERCE_BUILDING_AND_CONSTRUCTION_MATERIALS_GENERAL` * `ECOMMERCE_EQUIPMENT_TOOL_FURNITURE_AND_APPLIANCE_RENTAL_AND_LEASING` * `ECOMMERCE_FOOD_AND_NON_ALCOHOL_DRINKS_GROCERY_AND_SUPERMARKETS` * `ECOMMERCE_GENERAL_MERCHANDISE` * `ECOMMERCE_WOMEN_FASHION` * `FINANCIAL_SERVICE_FUND_WEALTH_MANAGEMENT_AND_INVESTMENT_HOLDING` * `OTHERS` * `PROFESSIONAL_SERVICES_DETECTIVE_AGENCIES_PROTECTIVE_SERVICES_AND_SECURITY_SERVICES_INCLUDING_ARMORED_CARS_AND_GUARD_DOGS` * `PROFESSIONAL_SERVICES_EMPLOYMENT_AGENCIES_AND_TEMPORARY_HELP_SERVICES` * `PROFESSIONAL_SERVICE_CONSULTING_AND_COACHING` * `PROFESSIONAL_SERVICE_DESIGN_AND_ARCHITECTURE` * `PROFESSIONAL_SERVICE_REAL_ESTATE_SALES_AND_BROKERAGE` ## Business address This structure applies to two separate requirement areas: `registered_address` (the entity's legal/formation address) and `operating_address` (where the business actually operates). | Field | Field name | Type | Required | Notes | | :--------------- | :-------------- | :---------- | :------- | :-------------------------------- | | Address line 1 | `addressLine1` | String(150) | Yes | | | Address line 2 | `addressLine2` | String(150) | No | | | City | `city` | String(50) | Yes | | | State / province | `stateProvince` | String(50) | Yes | | | Postal code | `postalCode` | String(10) | Yes | | | Country code | `countryCode` | String(2) | Yes | ISO 3166 country code (e.g. `US`) | ## Ownership Ownership and control are captured through the `associated_persons` requirement area, which covers everyone connected to the business — control persons, directors, signers, admins, shareholders, and beneficial owners. ### Associated persons Every individual connected to the business is submitted as one entry in this collection, tagged with one or more roles — see Associated person roles below. | Field | Field name | Type | Required | Notes | | :----------------------- | :------------------------------------- | :------------- | :------- | :------------------------------------------------ | | Roles | `roles` | Array (enum) | Yes | See Associated person roles below | | Title | `title` | String(150) | No | e.g. "CEO" | | Ownership percentage | `ownershipPercentage` | Integer | Yes | | | First name | `person.firstName` | String(150) | Yes | | | Last name | `person.lastName` | String(150) | Yes | | | Date of birth | `person.dateOfBirth` | Date | Yes | | | Citizenship | `person.citizenship` | String(2) | Yes | | | Email | `person.email` | String | Yes | | | Phone number | `person.phoneNumber` | String(50) | Yes | | | Tax number | `person.taxNumber` | String(50) | Yes | | | Address line 1 | `person.physicalAddress.addressLine1` | String(150) | Yes | | | Address line 2 | `person.physicalAddress.addressLine2` | String(150) | No | | | City | `person.physicalAddress.city` | String(50) | Yes | | | State / province | `person.physicalAddress.stateProvince` | String(50) | Yes | | | Postal code | `person.physicalAddress.postalCode` | String(10) | Yes | | | Country code | `person.physicalAddress.countryCode` | String(2) | Yes | ISO 3166 country code (e.g. `US`) | | Passport | `passport` | Document | No | Either this or both driver's license images below | | Driver's license (front) | `drivers_license_front` | Document | No | | | Driver's license (back) | `drivers_license_back` | Document | No | | | Proof of address | `address_proof` | Document (PDF) | Yes | | * `ADMIN` * `BENEFICIAL_OWNER` * `BUSINESS_ADMIN` * `BUSINESS_CONTROLLER` * `CONTROL_PERSON` * `DIRECTOR` * `REPRESENTATIVE` * `SHAREHOLDER` * `SIGNER` **At least one associated person must include the `SIGNER` role.** This is enforced as a collection-level rule, not on any individual field. ## PHP bank account For businesses settling in Philippine pesos, provide these bank account details via the `php_bank_account` requirement. | Field | Field name | Type | Required | Notes | | :------------------ | :------------------ | :---------- | :------- | :---------------------------------------- | | Account holder name | `accountHolderName` | String(150) | Yes | | | Account number | `accountNumber` | String(250) | Yes | | | Bank code | `bankCode` | String(50) | Yes | Provider-recognized PH bank/clearing code | | Bank name | `bankName` | String(150) | Yes | | ## Declaration An authorized signatory completes the application with a declaration and signature, certifying the accuracy of the information and agreeing to Meridian's terms. These terms cover disclosure of business activities, permitted use of the account, regulatory standing, ongoing disclosure of material changes (within 30 days), and consent to verification and screening. # Making a withdrawal Source: https://docs.mnai.com/products/meridian-accounts/guides/making-a-withdrawal Use the Transaction Intent API to preview an exchange rate and then commit a manual withdrawal from a Meridian balance. A withdrawal moves funds out of a customer's Meridian balance to a destination instrument, such as an external wallet. Withdrawals use the Transaction Intent API, a two-step flow. You first create an intent, which returns a preview with the exchange rate and fees but moves no money. You then commit that intent, which creates the actual transaction. ## Prerequisites Your program must be configured with the `RETAIN` routing policy. The routing policy determines what happens to funds when they arrive in a balance, and it is set during enrollment: * **`RETAIN`** — deposited funds stay on the balance until you move them. This is what makes manual withdrawals possible. * **`SWEEP`** — deposited funds are automatically forwarded to a preconfigured credit instrument. Meridian initiates the withdrawal for you, so there is nothing to trigger manually. The policy is submitted as a `routing_policy` entry in the Information Request during onboarding: ```json theme={null} { "requirementKey": "routing_policy", "entryId": "routing_policy:0", "data": { "routingPolicy": "RETAIN" } } ``` The withdrawal destination is also established during onboarding. For an external wallet destination, submit it alongside the routing policy: ```json theme={null} { "requirementKey": "destination_external_wallet_account_number", "entryId": "destination_external_wallet_account_number:0", "data": { "accountNumber": "5551112222" } } ``` If your program is configured with `SWEEP`, funds leave the balance automatically and this flow does not apply. Contact your Solutions Engineer if you need a `RETAIN` program configured. You also need: * an enrollment in `ACTIVE` status — see [Onboarding your customer](/products/meridian-accounts/guides/onboarding-your-customer) * an access token for the program context, with the `transactions:create` scope * a funded balance to withdraw from * the payment instrument IDs for both sides of the movement (see below) ## Identifying the two instruments Every intent moves funds from a **debit instrument** to a **credit instrument**, each identified by a payment instrument ID. Both come from a single call to `GET /v1/accounts/{accountId}`: ```json GET /v1/accounts/{accountId} theme={null} { "id": "uspay-pyj5tywsw7v0pzuyjirxvso2", "status": "ACTIVE", "currency": "USD", "displayName": "USD Balance", "routingPolicy": { "type": "RETAIN", "creditInstrument": { "externalWallet": { "id": "uspay-m7duekqg6biutaxkvg3mxqm6", "displayName": "BPI ****7890", "currency": "USDC_ETH", "mask": "7890" }, "instrumentType": "EXTERNAL_WALLET" } }, "availableBalance": "100.00", "actualBalance": "100.00", "depositInstructions": [] } ``` Map the two values straight onto the intent: | Read from the account | Pass as | What it is | | :------------------------------------------------- | :------------------- | :-------------------------------------- | | `id` | `debitInstrumentId` | The Meridian balance funds leave from | | `routingPolicy.creditInstrument.externalWallet.id` | `creditInstrumentId` | The destination that receives the funds | The destination is registered during onboarding. For an external wallet, it comes from the `destination_external_wallet_account_number` entry you submit in the Information Request. Reading it from the account means you never have to hardcode it. Use `GET /v1/accounts` to list a customer's accounts first if you need to choose one, then fetch that account for the withdrawal. Both instruments must be `ACTIVE` and belong to the authenticated caller, or the request returns `403`. The debit instrument must be a balance. Passing any other instrument type returns `400` with the message `Debit instrument must be a balance`. `creditInstrument` is omitted when the account has no withdrawal destination configured. If it is absent on a `RETAIN` account, that account cannot be withdrawn from. Treat it as a configuration issue and contact your Solutions Engineer. ## Making a withdrawal Call `POST /v1/transaction-intents` with both instruments and the amount. The response is a preview: it returns the exchange rate, the converted amount, and any fees, but no funds move yet. Show the customer the quoted `exchangeRate`, `creditAmount`, and `fees` from the intent so they can confirm before committing. Call `POST /v1/transaction-intents/{transactionIntentId}/commit` to execute the movement. This creates the transaction and returns the intent with a `relatedTransactionId`. Use the returned `relatedTransactionId` with `GET /v1/transactions/{transactionId}`, and consume [transaction webhooks](/products/meridian-accounts/webhooks/webhooks-transaction), to follow the withdrawal to completion. ### Step 1: Create the intent `fixedSide` tells Meridian which side of the conversion your `amount` refers to. Use `DEBIT` to specify how much leaves the balance, or `CREDIT` to specify how much should arrive at the destination. ```json Request theme={null} { "debitInstrumentId": "uspay-pyj5tywsw7v0pzuyjirxvso2", "creditInstrumentId": "uspay-m7duekqg6biutaxkvg3mxqm6", "fixedSide": "DEBIT", "amount": 100.0, "idempotencyKey": "withdrawal-2026-08-05-0001", "memo": "Manual withdrawal" } ``` The response is the intent preview, in `CREATED` status: ```json Response (201) theme={null} { "id": "usrem-b2k3e9qijzs3hl83265bmuue", "type": "WITHDRAWAL", "status": "CREATED", "createdAt": "2026-08-05T14:20:00Z", "updatedAt": "2026-08-05T14:20:00Z", "statusReason": null, "relatedTransactionId": null, "debitAmount": "100.00", "debitCurrency": "USD", "debitMemo": "Manual withdrawal", "debitInstrument": { "instrumentType": "ACCOUNT", "account": { "id": "uspay-pyj5tywsw7v0pzuyjirxvso2", "displayName": "USD Balance", "currency": "USD" } }, "creditAmount": "5600.00", "creditCurrency": "PHP", "creditInstrument": { "instrumentType": "EXTERNAL_WALLET", "externalWallet": { "id": "uspay-m7duekqg6biutaxkvg3mxqm6", "displayName": "Partner Wallet", "currency": "PHP", "mask": "2222" } }, "exchangeRate": "56.00", "fees": [ { "type": "PROCESSING", "label": "Processing fee", "amount": "1.00", "currency": "USD" } ] } ``` No funds have moved at this point. Keep the returned `id`. You need it to commit. ### Step 2: Commit the intent Commit with the **same** `idempotencyKey` you used to create the intent: ```json Request theme={null} { "idempotencyKey": "withdrawal-2026-08-05-0001" } ``` ```json Response (200) theme={null} { "id": "usrem-b2k3e9qijzs3hl83265bmuue", "type": "WITHDRAWAL", "status": "COMMITTED", "createdAt": "2026-08-05T14:20:00Z", "updatedAt": "2026-08-05T14:20:45Z", "relatedTransactionId": "ustxn-9fk2mqp4v7yc3xr8swld6bne", "debitAmount": "100.00", "debitCurrency": "USD", "creditAmount": "5600.00", "creditCurrency": "PHP", "exchangeRate": "56.00", "fees": [ { "type": "PROCESSING", "label": "Processing fee", "amount": "1.00", "currency": "USD" } ] } ``` The `relatedTransactionId` is the transaction Meridian created. Committing the same intent again returns the same result rather than creating a second transaction. ## Intent status values | Status | Meaning | | :---------- | :------------------------------------------------------ | | `CREATED` | The preview exists and can be committed | | `COMMITTED` | The intent was committed and a transaction was created | | `EXPIRED` | The quoted rate is no longer valid; create a new intent | | `CANCELED` | The intent was canceled and cannot be committed | | `FAILED` | The intent could not be processed; check `statusReason` | An intent holds a quoted exchange rate, so it does not stay valid indefinitely. If a customer takes too long to confirm, the intent reaches `EXPIRED` and committing it returns an error. Create a new intent to get a fresh rate. The intent does not return an expiry timestamp, so treat the quote as short-lived and commit promptly after the customer confirms. ## Idempotency The `idempotencyKey` is required on both calls, and the key you commit with must match the key the intent was created with. Use one unique key per withdrawal attempt, generated from your own reference for the withdrawal, and reuse it if you need to retry either call. This makes retries safe: a repeated request returns the original result instead of creating a duplicate withdrawal. ## Recommended integration behavior Your integration should: * confirm the account's `routingPolicy` is `RETAIN` and carries a `creditInstrument` before exposing withdrawal functionality in your UI * resolve both instrument IDs from the account at withdrawal time rather than hardcoding them, so a destination change is picked up automatically * show the quoted `exchangeRate`, `creditAmount`, and `fees` to the customer before committing * treat the intent as short-lived, and request a new one when an intent reaches `EXPIRED` * store the `idempotencyKey` with your own withdrawal record so retries reuse it * track completion through the `relatedTransactionId` and the [transaction webhook](/products/meridian-accounts/webhooks/webhooks-transaction), not the intent itself # Onboarding your customer Source: https://docs.mnai.com/products/meridian-accounts/guides/onboarding-your-customer Enroll customers into a program, complete required onboarding steps, and activate their Meridian Account. A Meridian Account always exists within the context of a program. In Meridian, that program relationship is represented by an enrollment. Before your customer can use a Meridian Account, you must enroll them into the correct program, collect any required information, and wait for the enrollment to become `ACTIVE`. ## Prerequisites Before you implement onboarding, decide: * whether your integration is `SINGLE_USER` or `MULTI_USER` * whether the current request is using partner HMAC authentication or a Meridian JWT bearer token Those choices determine how the target user is identified before enrollment starts. Review: * [Choose your integration model](/products/meridian-accounts/guides/choose-your-integration-model) * [Authentication overview](/products/meridian-accounts/guides/authentication-overview) * [Server-to-server authentication with HMAC](/products/meridian-accounts/guides/server-to-server-authentication-with-hmac) * [Client-server authentication with JWT](/products/meridian-accounts/guides/client-server-authentication-with-jwt) ## How Meridian account onboarding works Your integration is responsible for choosing the correct program for the customer. Once you create an access token in that program context, you can begin the enrollment orchestration for that customer. The Meridian user identity and the program enrollment are separate concerns. A customer may already have a Meridian `userId` but still need a new enrollment in the program you selected for the current flow. At a high level, the onboarding flow is: Determine which Meridian program the customer should be enrolled into. The program defines the context in which the Meridian Account will exist. Call `POST /v1/auth/token` to create an access token for the customer in the selected program context. Call `GET /v1/enrollment` to determine whether the customer already has an enrollment for that program. If the returned enrollment status is `INACTIVE`, call `POST /v1/enrollment/activate` to activate or resume the enrollment for that program. Meridian creates an Information Request, which is a schema-driven information collection system. Your integration should present and complete the latest Information Request until it is ready for submission. After the Information Request is submitted and approved, the enrollment status moves to `ACTIVE` and Meridian sends a webhook notification. ## Enrollment is the program-level record Think of the enrollment as the record that connects a Meridian user to a specific program. The same customer may exist in your system independently of Meridian. A Meridian Account is not ready until that customer has an active enrollment in the target program. `GET /v1/enrollment` always returns the enrollment state for the current program context. If the status is `INACTIVE`, the customer still needs enrollment activation and/or onboarding completion before you can use the account. ## First-time enrollment flow Use this sequence for a customer who is entering the program for the first time: 1. Create an access token with `POST /v1/auth/token`. 2. Call `GET /v1/enrollment`. 3. If the enrollment status is `INACTIVE`, call `POST /v1/enrollment/activate`. 4. Read the returned enrollment state and `latestInformationRequest` details. 5. Use the Information Request endpoints to collect the required customer information. 6. Submit the Information Request for review. 7. Listen for webhook updates until the enrollment becomes `ACTIVE`. A newly activated enrollment is typically returned as `INACTIVE` with a `latestInformationRequest` while onboarding is still in progress. The enrollment exists, but the customer still has required information to complete before the account can be used. ## Understanding the Information Request In Meridian Accounts, an Information Request is the mechanism used to collect the information and documentation required to complete enrollment. The Information Request is schema-driven, which means your integration should use the structure returned by the API to determine: * which steps to show * which fields are required * whether documents must be uploaded * whether the Information Request is complete and ready to submit Depending on the enrollment requirements, the Information Request may ask for business details, controller details, beneficial ownership information, or supporting documents. ## Information Request lifecycle Use the Information Request endpoints to drive onboarding from the current Meridian state instead of hardcoding your own form flow. ### 1. Find the active Information Request After you activate an enrollment, check the enrollment response for `latestInformationRequest`. If the customer already has an in-progress onboarding flow, use that Information Request ID to continue from the existing state. You can also call `GET /v1/information-requests` to list Information Requests in the current program context, then call `GET /v1/information-requests/{informationRequestId}` to load the full schema for the latest Information Request. ### 2. Render the Information Request from the schema Use `GET /v1/information-requests/{informationRequestId}` to load the current Information Request definition. The response is organized into steps and requirements. Use that response to determine: * which sections to show * which fields belong to each section * whether a requirement has a single entry or repeated entries * how each field must be collected * whether each step, requirement, or field is currently `MISSING`, `INVALID`, or `VALID` Do not assume every customer sees the same fields or the same number of steps. Render the onboarding flow from the returned schema. ### 3. Use the two collection modes Each Information Request item tells you how Meridian expects that field to be collected by using `collectionMode`. The two collection modes are: * `DIRECT_INPUT`: collect a value directly in your UI and send it with `PATCH /v1/information-requests/{informationRequestId}` * `FILE_UPLOAD`: collect one or more files and upload them with `POST /v1/information-requests/{informationRequestId}/file-uploads` For `DIRECT_INPUT` items, use the `directInput` schema on the item to understand the expected value shape. Meridian uses the JSON Schema standard for this definition. A direct-input item may represent a single value, or a structured object whose data fields you must collect together. For `FILE_UPLOAD` items, use the `fileUpload` configuration on the item to enforce Meridian's file requirements, including: * minimum and maximum file count * allowed MIME types * allowed file extensions * maximum file size Treat the API response as the source of truth for collection behavior. If an item is marked `DIRECT_INPUT`, do not treat it as a document upload. If it is marked `FILE_UPLOAD`, do not try to satisfy it with a text field. ### 4. Handle single and repeated requirements Each requirement also tells you whether it is `SINGLE` or `MULTIPLE`. * `SINGLE` means the requirement has one set of values * `MULTIPLE` means the customer may need to provide repeated entries, such as multiple beneficial owners For `MULTIPLE` requirements, use the metadata returned by the API to guide the experience: * `addInstanceLabel` to label the add action * `instanceHeaderPrefix` to label each repeated entry * `minInstanceCount` and `maxInstanceCount` to control how many entries are allowed * `instances` to render the values already collected When updating a repeated requirement, include `instanceIndex` in each patch item so Meridian can associate the value with the correct entry. ### 5. Save values and re-read validation Use `PATCH /v1/information-requests/{informationRequestId}` to save direct-input values as the customer progresses through onboarding. Each patch item identifies: * `requirementKey` for the requirement being updated * `itemKey` for the specific field * `value` for the field value * `instanceIndex` when the requirement supports multiple entries After saving values or uploading files, read the updated Information Request response and use Meridian's validation state to decide what happens next. The API guides valid collection in three ways: * step-level `validationStatus` shows whether the section is complete * requirement-level `validationStatus` shows whether that requirement is satisfied * field-level validation on current values shows whether an individual value is missing, invalid, or valid Use these validation states to decide: * which errors to show * which sections still need attention * whether the customer can continue * whether the Information Request is ready to submit Do not decide completeness from local form rules alone. Use the validation returned by Meridian as the source of truth. ### 6. Submit for review When the Information Request is complete, call `POST /v1/information-requests/{informationRequestId}/submit` to send it for review. A submitted Information Request does not mean the customer is fully onboarded yet. After submission, the Information Request may move through review before the enrollment becomes `ACTIVE`. ### 7. Wait for status updates After submission, continue monitoring both enrollment and Information Request state. Your integration should: * consume `information_request_status_updated` webhooks to detect review progress * consume `enrollment_status_updated` webhooks to know when onboarding is complete * treat enrollment `ACTIVE` as the final signal that the Meridian Account is ready to use If your system needs to recover state after a webhook delivery issue, reload the enrollment and latest Information Request from the API and continue from the latest returned state. ## When the account is ready The Meridian Account is ready for use only after the enrollment reaches `ACTIVE`. Until then, the customer is still in onboarding even if Meridian has created the enrollment record. Once the enrollment becomes `ACTIVE`: * the required onboarding flow is complete * Meridian sends an enrollment webhook * your integration can treat the Meridian Account as ready for use ## Recommended integration behavior Your integration should: * store the program context used for onboarding * treat enrollment status `INACTIVE` as the condition for calling `POST /v1/enrollment/activate` * drive the onboarding UI from the latest Information Request schema * treat enrollment status `ACTIVE` as the final ready-for-use signal, and `INACTIVE` as the activation or onboarding-in-progress state * wait for enrollment `ACTIVE` before enabling account functionality * consume enrollment and Information Request webhooks so your system stays synchronized with Meridian state ## Next steps After implementing this flow, review: * [Getting started](/products/meridian-accounts/guides/getting-started) * [Choose your integration model](/products/meridian-accounts/guides/choose-your-integration-model) * [Authentication overview](/products/meridian-accounts/guides/authentication-overview) * [API reference overview](/products/meridian-accounts/api-reference) * [Webhooks overview](/products/meridian-accounts/webhooks) * [Enrollment webhooks](/products/meridian-accounts/webhooks/webhooks-enrollment) * [Information Request webhooks](/products/meridian-accounts/webhooks/webhooks-information-request) # Server-to-server authentication with HMAC Source: https://docs.mnai.com/products/meridian-accounts/guides/server-to-server-authentication-with-hmac Use Meridian partner credentials and an HMAC SHA-256 signature when your backend calls the Meridian Accounts API directly. Use partner HMAC authentication when your backend calls Meridian directly with a Meridian API Key and Secret. This is the mechanism that proves partner identity. It is required for `POST /v1/auth/token` and for any other API operation that supports partner-key authentication. ## Required headers Partner-authenticated requests use these headers: | Header | Required | Use | | :---------------------- | :---------------- | :------------------------------------------------------------- | | `X-Meridian-Api-Key` | Yes | Issued by Meridian for the environment | | `X-Meridian-Program-Id` | Yes | Identifies the program context for the request | | `X-Meridian-Timestamp` | Yes | Timestamp used to validate request freshness and the signature | | `X-Meridian-Signature` | Yes | HMAC SHA-256 signature for the request | | `X-Meridian-User-Id` | `MULTI_USER` only | Identifies the Meridian user targeted by the request | For `SINGLE_USER` integrations, omit `X-Meridian-User-Id`. Meridian uses the single configured user for that partner key. ## When to send `X-Meridian-User-Id` Your integration model determines whether `X-Meridian-User-Id` is required: * `MULTI_USER`: required on partner-key calls * `SINGLE_USER`: must be omitted on partner-key calls This rule applies to token creation and to other partner-key API calls that accept direct partner authentication. ## Constructing the signature Meridian authenticates partner-key requests with an HMAC SHA-256 signature. `X-Meridian-Program-Id` is required on the request, but it is not part of the canonical string used for signing. ### Canonical string Build the canonical string by concatenating these values in this exact order: 1. `API Key` 2. `Timestamp` 3. `HTTP method` 4. `Request path` 5. `Request body` For example, for `POST /v1/auth/token`: * the method is `POST` * the path is `/v1/auth/token` * the body is the exact JSON string sent on the wire If there is no request body, use an empty string for the body portion of the canonical string. ### How to compute the signature 1. Read your Meridian API Key and API Secret for the environment you are calling. 2. Generate a timestamp in milliseconds and send it as `X-Meridian-Timestamp`. 3. Serialize the JSON request body exactly as it will be sent. 4. Concatenate the API Key, timestamp, method, path, and serialized body to form the canonical string. 5. Compute the HMAC SHA-256 of that canonical string using your API Secret as the key. 6. Hex-encode the result and send it as `X-Meridian-Signature`. ### Example If your request is: ```http theme={null} POST /v1/auth/token X-Meridian-Api-Key: mer_test_123 X-Meridian-Program-Id: prog_abc123 X-Meridian-User-Id: user_123 X-Meridian-Timestamp: 1735689600000 Content-Type: application/json ``` ```json theme={null} { "grant_type": "client_credentials" } ``` Then the canonical string is: ```text theme={null} mer_test_1231735689600000POST/v1/auth/token{"grant_type":"client_credentials"} ``` Compute the HMAC SHA-256 of that canonical string using your API Secret, then hex-encode the output and send the result in `X-Meridian-Signature`. ### Example code ```javascript javascript theme={null} import crypto from "crypto"; const apiKey = process.env.MERIDIAN_API_KEY; const apiSecret = process.env.MERIDIAN_API_SECRET; const programId = process.env.MERIDIAN_PROGRAM_ID; const method = "POST"; const path = "/v1/auth/token"; const timestamp = Date.now().toString(); const body = JSON.stringify({ grant_type: "client_credentials", }); const canonical = apiKey + timestamp + method + path + body; const signature = crypto .createHmac("sha256", apiSecret) .update(canonical) .digest("hex"); const headers = { "Content-Type": "application/json", "X-Meridian-Api-Key": apiKey, "X-Meridian-Program-Id": programId, "X-Meridian-User-Id": "user_123", "X-Meridian-Timestamp": timestamp, "X-Meridian-Signature": signature, }; ``` ```kotlin kotlin theme={null} import javax.crypto.Mac import javax.crypto.spec.SecretKeySpec fun buildMeridianTokenHeaders( apiKey: String, apiSecret: String, programId: String, userId: String?, body: String ): Map { val method = "POST" val path = "/v1/auth/token" val timestamp = System.currentTimeMillis().toString() val canonical = apiKey + timestamp + method + path + body val mac = Mac.getInstance("HmacSHA256").apply { init(SecretKeySpec(apiSecret.toByteArray(), "HmacSHA256")) } val signature = mac.doFinal(canonical.toByteArray()) .joinToString("") { "%02x".format(it) } return buildMap { put("Content-Type", "application/json") put("X-Meridian-Api-Key", apiKey) put("X-Meridian-Program-Id", programId) if (userId != null) { put("X-Meridian-User-Id", userId) } put("X-Meridian-Timestamp", timestamp) put("X-Meridian-Signature", signature) } } ``` ```bash bash theme={null} API_KEY="mer_test_123" API_SECRET="your_api_secret" PROGRAM_ID="prog_abc123" USER_ID="user_123" TIMESTAMP="$(python3 -c 'import time; print(int(time.time() * 1000))')" BODY='{"grant_type":"client_credentials"}' CANONICAL="${API_KEY}${TIMESTAMP}POST/v1/auth/token${BODY}" SIGNATURE="$(printf '%s' "$CANONICAL" | openssl dgst -sha256 -hmac "$API_SECRET" | sed 's/^.* //')" ``` ## Token minting flow Use `POST /v1/auth/token` when your backend needs a Meridian access token for a user. * `SINGLE_USER`: omit `X-Meridian-User-Id`; Meridian uses the user configured for that partner integration * `MULTI_USER`: include `X-Meridian-User-Id` to identify the target Meridian user Depending on your flow, the token request body can: * use `client_credentials` for a target user * use `refresh_token` to exchange a refresh token for a new access token For a first-time `MULTI_USER` flow, create the Meridian user first with `POST /v1/auth/users`, then mint the token for that `userId`. If `POST /v1/auth/users` fails because `externalId` already belongs to an existing Meridian user, retry token creation only if you already know the stored `userId` from your own customer-to-user mapping. ## Partner-key access beyond token minting Some Meridian Accounts endpoints support direct partner-key authentication in addition to JWT bearer authentication. When the OpenAPI for an endpoint lists both partner API Key security and JWT security, your backend may call that endpoint directly with HMAC-signed partner credentials. For client-facing account access, prefer JWT bearer authentication even when partner-key auth is technically supported. ## Common authentication errors The most common causes of HMAC authentication failures are: * omitting `X-Meridian-Api-Key` or another required partner header * concatenating canonical string fields in the wrong order * signing a different JSON string than the one actually sent * including `X-Meridian-Program-Id` in the canonical string * sending `X-Meridian-User-Id` for a `SINGLE_USER` integration * omitting `X-Meridian-User-Id` for a `MULTI_USER` integration * using a timestamp outside the allowed 60-second window * using Sandbox credentials against production, or production credentials against Sandbox The signature is sensitive to field order, whitespace, and request path. If the request body or path differs from what you signed, Meridian will reject the request. ## Related guides * [Choose your integration model](/products/meridian-accounts/guides/choose-your-integration-model) * [Authentication overview](/products/meridian-accounts/guides/authentication-overview) * [Client-server authentication with JWT](/products/meridian-accounts/guides/client-server-authentication-with-jwt) # Account Source: https://docs.mnai.com/products/meridian-accounts/webhooks/webhooks-account products/meridian-accounts/webhooks/webhooks.json webhook account Learn when account webhooks are sent and what data they include. # Enrollment Source: https://docs.mnai.com/products/meridian-accounts/webhooks/webhooks-enrollment products/meridian-accounts/webhooks/webhooks.json webhook enrollment Learn when enrollment webhooks are sent and what data they include. # Information Request Source: https://docs.mnai.com/products/meridian-accounts/webhooks/webhooks-information-request products/meridian-accounts/webhooks/webhooks.json webhook information_request Learn when Information Request webhooks are sent and what data they include. # Overview Source: https://docs.mnai.com/products/meridian-accounts/webhooks/webhooks-overview Learn how Meridian Accounts delivers webhook notifications, including security, retries, and delivery behavior. ## Configuration Meridian configures webhook endpoints during provisioning. You can configure a separate URL for each webhook event type. If your integration needs different handling per event, set each webhook URL to the appropriate handler endpoint. ## Event routing Use the `X-Meridian-Resource-Type` and `X-Meridian-Event-Type` headers to identify the webhook before you parse the body. These headers tell you which resource the event belongs to and which schema to use. | Header | Example values | Use | | :------------------------- | :------------------------------------------------------------ | :-------------------------------------------------- | | `X-Meridian-Resource-Type` | `enrollment`, `information_request`, `transaction`, `account` | Routes the payload to the correct resource handler | | `X-Meridian-Event-Type` | See the event types table below | Selects the specific event parser for that resource | | Resource | Event types | | :-------------------- | :-------------------------------------------------- | | `enrollment` | `enrollment_status_updated` | | `information_request` | `information_request_status_updated` | | `transaction` | `transaction_created`, `transaction_status_updated` | | `account` | `account_created`, `account_status_updated` | Read these headers first, then deserialize the request body with the matching webhook schema. This lets your handler reject unexpected combinations early and keeps your routing logic aligned with the payload shape. ## Security Webhook requests use HMAC SHA-256 with a dedicated Webhook API Key and Secret. Each request includes the following headers: | Header | Description | | :--------------------- | :------------------------------------------------------------- | | `X-Meridian-Api-Key` | Issued by Meridian for webhook delivery | | `X-Meridian-Timestamp` | Current time in milliseconds, within 60 seconds of the request | | `X-Meridian-Signature` | HMAC SHA-256 signature | Validate the signature on every inbound request before processing the payload. Use the same canonical string construction described in [Server-to-server authentication with HMAC](/products/meridian-accounts/guides/server-to-server-authentication-with-hmac). ## Delivery and retries Meridian considers a webhook delivered when your endpoint returns an HTTP `2xx` response within **10 seconds**. If the request times out or returns a non-`2xx` response, Meridian retries using exponential backoff starting at 2 seconds, up to **10 attempts**. Because retries can produce duplicate deliveries, your webhook handler should be idempotent. Treat every webhook as potentially arriving out of order. Store the status and timestamp you already know for each enrollment, Information Request, account, or transaction. Only overwrite when the incoming payload reflects a newer state or a valid progression (for example, `DRAFT` → `PENDING_REVIEW` → `COMPLETED`). Log non-`2xx` responses and return as fast as possible; even short delays may trigger retries that redeliver the same event. # Transaction Source: https://docs.mnai.com/products/meridian-accounts/webhooks/webhooks-transaction products/meridian-accounts/webhooks/webhooks.json webhook transaction Learn when transaction webhooks are sent and what data they include. # Authentication Source: https://docs.mnai.com/products/payout-origination/authentication Secure your API requests using HMAC SHA-256 signatures with pre-shared API keys, including step-by-step instructions and code examples. * All API calls use an HMAC authentication strategy with pre-shared API and secret keys. * Sandbox and production environments have different API credentials. Do not reuse across environments. * All requests must use HTTPS (TLS 1.2 or higher). * You must allow outbound connections to the Payout Origination API base URLs. * IP whitelisting may be required for production access depending on your program configuration. Meridian provides details if applicable. Every request must include the following headers: | Header | Description | | :---------------------- | :---------------------------------------------------------------------- | | `X-Meridian-Api-Key` | Issued by Meridian | | `X-Meridian-Program-Id` | Identifies your Meridian program | | `X-Meridian-Timestamp` | Current time in milliseconds (must be within 60 seconds of the request) | | `X-Meridian-Signature` | HMAC SHA-256 signature | Meridian issues your API Key and Secret during Sandbox and production provisioning. When Meridian receives the request, it validates the signature. If the signature does not match, the API immediately returns a `401 Unauthorized` response. ## Constructing the signature Compute the signature as an HMAC SHA-256 hash over a canonical string that concatenates the following values in order: | Signature component | Notes | | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `API-Key` | Same value as `X-Meridian-Api-Key` | | `Timestamp` | Same value as `X-Meridian-Timestamp` | | `Request method` | Example: `GET`, `POST`, `PATCH` | | `Request path` | Including any parameters. For example:
`/v1/payout-origination/{format}`, `/v1/transactions`, or `/v1/transactions/txn_01JVY8Y4N9X2M6S5Q7T1` | | `Body contents` | The exact request body sent on the wire. For Payout Origination POST requests, this is the encoded payload. If there is no body, this can be an empty string. | `X-Meridian-Program-Id` is required on every request but is not included in the canonical signing string.
Compute the HMAC using the language-appropriate crypto library with the secret key. Make sure you compute the HMAC with the API Secret that Meridian issued to you. Hash the final value using the SHA-256 algorithm and set it as the `X-Meridian-Signature` header. ```kotlin kotlin theme={null} import javax.crypto.Mac import javax.crypto.spec.SecretKeySpec fun buildMeridianSignature(apiKey: String, apiSecret: String, programId: String, method: String, path: String, body: String = ""): Map { val timestamp = System.currentTimeMillis().toString() val canonical = apiKey + timestamp + method + path + body val mac = Mac.getInstance("HmacSHA256").apply { init(SecretKeySpec(apiSecret.toByteArray(), "HmacSHA256")) } val signature = mac.doFinal(canonical.toByteArray()) .joinToString("") { "%02x".format(it) } return mapOf( "X-Meridian-Api-Key" to apiKey, "X-Meridian-Program-Id" to programId, "X-Meridian-Timestamp" to timestamp, "X-Meridian-Signature" to signature, "Content-Type" to "text/plain" ) } ``` ```swift swift theme={null} import CryptoKit import Foundation func buildMeridianSignature(apiKey: String, apiSecret: String, programId: String, method: String, path: String, body: String = "") -> [String: String] { let timestamp = String(Int(Date().timeIntervalSince1970 * 1000)) let canonical = apiKey + timestamp + method + path + body let key = SymmetricKey(data: Data(apiSecret.utf8)) let signature = HMAC.authenticationCode(for: Data(canonical.utf8), using: key) .map { String(format: "%02x", $0) }.joined() return [ "X-Meridian-Api-Key": apiKey, "X-Meridian-Program-Id": programId, "X-Meridian-Timestamp": timestamp, "X-Meridian-Signature": signature, "Content-Type": "text/plain" ] } ``` ```javascript postman theme={null} // ─── Config ────────────────────────────────────────────────────────────────── // Set these as collection variables (never hardcode secrets) const apiKey = pm.variables.get("meridianApiKey"); const apiSecret = pm.variables.get("meridianApiSecret"); // ─── Timestamp (milliseconds) ───────────────────────────────────────────────── const timestamp = Date.now().toString(); // ─── Request components ─────────────────────────────────────────────────────── const method = pm.request.method; // e.g. "POST" const url = pm.request.url; const qs = url.getQueryString(); const path = url.getPath() + (qs ? "?" + qs : ""); // e.g. "/v1/payout-origination/pacs008" or "/v1/transactions" const body = pm.request.body?.raw ?? ""; // raw Base64 payload for POST requests // ─── Canonical string ───────────────────────────────────────────────────────── const canonical = apiKey + timestamp + method + path + body; // ─── HMAC-SHA256 signature ──────────────────────────────────────────────────── const signature = CryptoJS.HmacSHA256(canonical, apiSecret).toString(CryptoJS.enc.Hex); // ─── Inject into request headers ───────────────────────────────────────────── pm.variables.set("meridianTimestamp", timestamp); pm.variables.set("meridianSignature", signature); pm.request.headers.add({ key: "X-Meridian-Program-Id", value: pm.variables.get("meridianProgramId") }); // ─── Debug ──────────────────────────────────────────────────────────────────── console.log("Timestamp :", timestamp); console.log("Canonical :", canonical); console.log("Signature :", signature); ```
## Common errors Meridian returns 401 Unauthorized for requests with missing or invalid signatures. The most common causes are: * Canonical string fields concatenated in the wrong order. * Timestamp too far from the server's current time. * Forgetting to compute the HMAC with your unique Secret Key. * Signing a different Base64 string than the one actually sent. * Forgetting to SHA-256 hash the final string. * Including `X-Meridian-Program-Id` in the canonical string. # Base64 payload encoding Source: https://docs.mnai.com/products/payout-origination/base64-payload-encoding Learn how to prepare the Base64-encoded payload you send to the Payout Origination API. Use this guide when you need to turn a complete message payload into the `text/plain` request body the Payout Origination API accepts. The API does not accept raw plaintext XML or JSON. You must Base64-encode the full source payload and send the encoded payload as the raw request body. ## What you encode Encode the complete payload you want Meridian to process. That means: * finish building the payload first * validate the payload content before encoding * encode the full payload as one artifact * send the encoded result as-is Do not: * wrap the source payload in another envelope before encoding * split the message into separate fields * encode only part of the payload * modify the encoded payload after it is produced Base64 encoding is not encryption. It preserves the source payload as plain reversible data. Rely on HTTPS, access controls, and careful logging practices to protect message contents. ## Recommended flow Produce the exact payload Meridian should process. If you need the expected message shape for your format, review [Supported formats](/products/payout-origination/supported-formats). Base64-encode the full payload and produce a single ASCII string. Send the encoded payload directly as the HTTP request body. Send the body with: * `Content-Type: text/plain` * Meridian HMAC authentication headers * a separate `Idempotency-Key` header ## Encoding guidance Apply Base64 to the exact bytes of the source payload you want Meridian to process. That means: * use a stable character encoding such as UTF-8 for the source payload * Base64-encode the entire payload in one step * hash and sign the exact Base64 string that you send * avoid inserting extra whitespace, line wrapping, or formatting changes after encoding ## Example payloads Example XML to encode: ```xml theme={null}
20260407-000123
``` Base64-encode it: ```bash theme={null} base64 -i message.xml ``` Example result: ```text theme={null} PE1lc3NhZ2U+CiAgPEhlYWRlcj4KICAgIDxJZD4yMDI2MDQwNy0wMDAxMjM8L0lkPgogIDwvSGVhZGVyPgo8L01lc3NhZ2U+ ``` Send that Base64 string as the raw HTTP body. Example JSON to encode: ```json theme={null} { "entityId": "ent_01JVY8Y4N9X2M6S5Q7T1", "beneficiaryId": "bnf_01JVY8Y4N9X2M6S5Q7T2", "amount": "1250.00", "currency": "USD", "reference": "invoice-100045" } ``` ## Handling guidance * Keep the source payload in a stable encoding such as UTF-8 before Base64-encoding it. * Treat the Base64 output as an opaque string. * Send the encoded payload as the raw request body, not as a JSON property. * Log identifiers such as `messageId` and your own business reference instead of logging raw payment contents. * Reuse the same Base64 payload only when retrying the same logical submission with the same idempotency intent. ## Common mistakes | Mistake | Result | | :--------------------------------------------- | :------------------------------------------------------------------------------------ | | Sending plaintext XML with `application/xml` | Meridian rejects the request because the API expects `text/plain` with Base64 content | | Sending plaintext JSON with `application/json` | Meridian rejects the request because the API expects `text/plain` with Base64 content | | Wrapping the Base64 string in JSON | Signature and body handling become inconsistent with the documented request shape | | Encoding a partial payload | Meridian receives an artifact that does not represent the original business message | | Changing the body after signing | HMAC verification fails | ## Relationship to retrieval After Meridian accepts the request, you can query the latest processing state with `GET /v1/transactions`, `GET /v1/transactions?messageId=...`, or `GET /v1/transactions/{id}`. These GET endpoints return transaction records and status information. They do not return the stored Base64 payload, and they do not replace your responsibility to manage the original source payload safely in your own systems where required. ## Related pages * [Overview](/products/payout-origination/overview) * [Supported formats](/products/payout-origination/supported-formats) * [Authentication](/products/payout-origination/authentication) # JSON format Source: https://docs.mnai.com/products/payout-origination/json-format Understand the Base64-encoded JSON payload Meridian accepts when you submit `POST /v1/payout-origination/json`. Use this page when you want to originate a payout from a Base64-encoded JSON payload instead of an ISO 20022 XML document. Use `json` as the `{format}` path parameter when submitting this message type: `POST /v1/payout-origination/json` Meridian accepts a single JSON object as the source payload for this format. Build the JSON document first, then Base64-encode that exact JSON string and send it as the raw `text/plain` request body. ## Meridian intake requirements | Item | Requirement | | :--------------- | :------------------------------------------------------------------------- | | Path slug | `json` | | Document format | JSON object | | Submission model | Base64-encode the full JSON payload and send it as the request body | | Content type | `text/plain` | | Required fields | `entityId`, `beneficiaryId`, `amount`, `currency`, `reference` | | Required values | All five fields must be present and each field must have a non-empty value | | Transformations | Do not wrap the payload in another JSON envelope after encoding | ## Required payload shape The decoded JSON payload must contain all of these fields. All values are required. | Field | Type | Description | | :-------------- | :--------------- | :------------------------------------------------------------- | | `entityId` | string | Meridian entity identifier for the payout originator | | `beneficiaryId` | string | Meridian beneficiary identifier that should receive the payout | | `amount` | string or number | Payout amount before Base64 encoding | | `currency` | string | Three-letter payout currency code | | `reference` | string | Your business reference for the payout | ## Example JSON payload ```json theme={null} { "entityId": "ent_01JVY8Y4N9X2M6S5Q7T1", "beneficiaryId": "bnf_01JVY8Y4N9X2M6S5Q7T2", "amount": "1250.00", "currency": "USD", "reference": "invoice-100045" } ``` ## Practical expectations When you prepare a JSON payout origination payload: * include all five required fields with values * use the exact `entityId` and `beneficiaryId` values Meridian assigned * preserve the JSON bytes you intend to submit before encoding * Base64-encode the complete JSON object in one step * send the encoded output as the raw request body Do not: * send plaintext JSON with `application/json` * split the payload fields into query parameters or headers * Base64-encode one field at a time * wrap the Base64 string in another JSON property ## Before you submit Before calling `POST /v1/payout-origination/json`: 1. Build the final JSON object with `entityId`, `beneficiaryId`, `amount`, `currency`, and `reference`. 2. Validate that all five fields are present and that all five values are populated in your own system. 3. Base64-encode the full JSON document. 4. Send the encoded payload with HMAC authentication headers and a separate `Idempotency-Key` header. ## Transaction model mapping Use this section to understand how Meridian maps the decoded JSON payload into the transaction model returned by `GET /v1/transactions` and `GET /v1/transactions/{id}`. | Transaction field | JSON source | Notes | | :--------------------------------------- | :----------------------------------- | :------------------------------------------------------------------------- | | `externalId` | `reference` | Meridian exposes your reference as the business-facing external identifier | | `amounts.debit.amount` | `amount` | Uses the submitted payout amount | | `amounts.debit.currency` | `currency` | Uses the submitted currency | | `amounts.credit.amount` | `amount` | Currently mirrors the same payout amount | | `amounts.credit.currency` | `currency` | Currently mirrors the same payout currency | | `creditInstrument.beneficiaryAccount.id` | `beneficiaryId` | Uses the beneficiary identifier in the payload | | `source.type` | Internal value `external_submission` | Current documented behavior for JSON-ingested transactions | Meridian assigns these transaction fields directly; they are not sourced from the JSON payload: * `id` * `messageId` * `status` * `statusReason` * `createdAt` * `updatedAt` * `relatedTransactionId` ## Related pages * [Supported formats](/products/payout-origination/supported-formats) * [Base64 payload encoding](/products/payout-origination/base64-payload-encoding) * [Authentication](/products/payout-origination/authentication) # Overview Source: https://docs.mnai.com/products/payout-origination/overview Submit payout requests to Meridian for asynchronous validation and processing. Use the Payout Origination API when you need to deliver a payout message to Meridian as a Base64-encoded payload. You Base64-encode the original message payload, sign the request with Meridian HMAC headers, and submit it to the ingestion endpoint. Meridian accepts the encoded payload, stores it, and processes it asynchronously. If you need help building the request body, see [Base64 payload encoding](/products/payout-origination/base64-payload-encoding). ## What this API does The Payout Origination API is designed for one job: receiving a full message as an encoded payload and moving it into Meridian's processing pipeline. Use this API when you need to: * send a complete payment payload without splitting it across API fields * preserve the exact message bytes while using a simple text transport format * list resulting transactions and read their latest processing status * protect against duplicate submissions with an idempotency key * receive an acknowledgement immediately instead of waiting for full processing to finish Base64 encoding does not encrypt the message or provide confidentiality. Use HTTPS in transit and avoid logging or exposing the raw payload in client and server logs. ## Request flow Encode the complete source payload as a Base64 string. Send the encoded payload as the raw request body with `Content-Type: text/plain`. Authenticate the request with Meridian HMAC headers: * `X-Meridian-Api-Key` * `X-Meridian-Timestamp` * `X-Meridian-Signature` Send `Idempotency-Key` as a separate request header. Use a unique `Idempotency-Key` for each business submission so retries do not create duplicates. Send `POST /v1/payout-origination/{format}`, where `format` identifies the message type. Meridian validates the headers, checks that the body is valid Base64, stores the encoded payload, and queues the message for asynchronous processing. A successful request returns `202 Accepted` with a Meridian `messageId`, a `transactionId`, an initial `RECEIVED` status, and a `receivedAt` timestamp. Meridian then continues downstream processing after the API request completes. ## What a successful response means `202 Accepted` means Meridian has safely accepted the encoded payload for processing. It does not mean the message has already been validated or completed. Store the returned `messageId` and `transactionId`. Use them for later status tracking, reconciliation, and transaction lookup. Example accepted response: ```json theme={null} { "messageId": "msg_01JVY8Y4N9X2M6S5Q7T1", "transactionId": "txn_01JVY8Y4N9X2M6S5Q7T1", "status": "RECEIVED", "receivedAt": "2026-04-07T20:11:42Z" } ``` ## Endpoint summary ### Supported format values | `{format}` value | Source payload | | :--------------- | :---------------------------------------------------------------------------------- | | `pacs008` | ISO 20022 `pacs.008.*` XML document | | `json` | JSON object with `entityId`, `beneficiaryId`, `amount`, `currency`, and `reference` | ### Submit an encoded message | Item | Value | | :--------------------- | :------------------------------------------------------------------- | | Method | `POST` | | Path | `/v1/payout-origination/{format}` | | Content type | `text/plain` | | Request body | Base64 string containing the full message payload | | Authentication headers | `X-Meridian-Api-Key`, `X-Meridian-Timestamp`, `X-Meridian-Signature` | | Idempotency header | `Idempotency-Key` | | Success response | `202 Accepted` | ### List transactions | Item | Value | | :------------------------ | :-------------------------------------------------------------------------------- | | Method | `GET` | | Path | `/v1/transactions` | | Authentication headers | `X-Meridian-Api-Key`, `X-Meridian-Timestamp`, `X-Meridian-Signature` | | Optional query parameters | `pageIndex`, `pageSize`, `messageId` | | Success response | `200 OK` | | Response body | JSON containing paginated transaction records with their latest processing status | ### Get a transaction by ID | Item | Value | | :--------------------- | :------------------------------------------------------------------- | | Method | `GET` | | Path | `/v1/transactions/{id}` | | Authentication headers | `X-Meridian-Api-Key`, `X-Meridian-Timestamp`, `X-Meridian-Signature` | | Success response | `200 OK` | | Response body | JSON containing the transaction record and latest processing status | ## Error handling Build your client to handle these common response classes: | Status | Meaning | | :----- | :---------------------------------------------------------------------------------------------------------------------- | | `400` | The request is malformed. Common causes include a missing body, invalid timestamp format, or an invalid Base64 payload. | | `401` | HMAC authentication failed. The signature may be invalid or the timestamp may be outside the allowed replay window. | | `403` | Your credentials are valid, but you are not authorized to submit this sender or message type. | | `404` | The supplied transaction `id` does not match a stored transaction when you use the detail endpoint. | | `409` | Meridian detected a duplicate submission, usually from a reused `Idempotency-Key`. | | `413` | The Base64 payload exceeds the allowed size. | | `415` | `Content-Type` is not `text/plain`. | | `422` | The request was structurally valid but failed early semantic validation. | ## Integration guidance * Treat the request body as an opaque Base64 string. Do not transform the message content before submission. * Use the path slug that matches the decoded payload you are sending. * Generate a fresh idempotency key for each new message and reuse it only when retrying the same submission. * Keep your system clock accurate. Timestamp skew can cause authentication failures. * Store the Meridian `messageId`, returned `transactionId`, your own business reference, and the idempotency key together for audit and reconciliation. * Hash and sign the exact Base64 string you send on the wire. * Use `GET /v1/transactions` to reconcile batches of transactions. * Use `GET /v1/transactions?messageId=...` when you want to find the transaction created from a specific submission. * Use `GET /v1/transactions/{id}` when you already know the Meridian internal transaction ID. ## Next steps * Review [Supported formats](/products/payout-origination/supported-formats) for the message types Meridian accepts. * Review [Base64 payload encoding](/products/payout-origination/base64-payload-encoding) for practical guidance on building the request body. * Review [Authentication](/products/payout-origination/authentication) for request signing details. * Use the API reference for the exact request and response schema. # pacs.008 format Source: https://docs.mnai.com/products/payout-origination/pacs008-format Understand the pacs.008 XML document Meridian expects you to Base64-encode and submit to the Payout Origination API. Use this page to understand the message document you send to the Payout Origination API before you Base64-encode it. Use `pacs008` as the `{format}` path parameter when submitting this message type: `POST /v1/payout-origination/pacs008` Meridian accepts `pacs.008.*` as one of the supported payout origination formats. You must submit the full original ISO 20022 XML document as a single opaque payload. Do not split the message into API fields or convert it into another structure. ## Meridian intake requirements | Item | Requirement | | :----------------- | :---------------------------------------------------------------------------- | | Message family | `pacs.008.*` | | Document format | ISO 20022 XML | | Submission model | Base64-encode the full message document and send it as the request body | | Content type | `text/plain` | | Transaction blocks | Exactly one `CdtTrfTxInf` block per submitted message | | Transformations | Do not flatten, normalize, or partially serialize the message before encoding | ## What Meridian means by full pacs.008 Meridian expects the complete business message content for the payment instruction you are submitting. That means you should preserve: * the original XML structure * the version-specific namespace * the element hierarchy * the values and formatting used in the original message The API treats the Base64 body as an opaque artifact at intake. Meridian does not expect you to map `pacs.008` fields into separate JSON properties. ## High-level document structure A `pacs.008` message is an ISO 20022 customer credit transfer instruction. The exact variant depends on the message version you use, but the structure is typically organized like this: ```xml theme={null} ... ... ``` In most integrations: * `Document` is the XML root element * `FIToFICstmrCdtTrf` contains the credit transfer message * `GrpHdr` contains message-level metadata * `CdtTrfTxInf` contains the payment transaction details Depending on the `pacs.008` version and network rules, the document may include one or more transaction blocks and additional optional elements. For Meridian Payout Origination, the submitted message must contain exactly one `CdtTrfTxInf` block. Messages with multiple transaction blocks are outside the documented contract for this API. ## Common sections The table below describes the sections most integrators will recognize in a `pacs.008` document. Exact child elements vary by schema version and implementation profile. | Section | Purpose | | :-------------------- | :-------------------------------------------------------------------------------------- | | `GrpHdr` | Message-level identifiers, creation time, settlement information, and transaction count | | `PmtId` | End-to-end and transaction reference identifiers | | `IntrBkSttlmAmt` | Interbank settlement amount and currency | | `IntrBkSttlmDt` | Settlement date | | `Dbtr` and `DbtrAcct` | Debtor party and account information | | `DbtrAgt` | Debtor agent or sending institution | | `CdtrAgt` | Creditor agent or receiving institution | | `Cdtr` and `CdtrAcct` | Creditor party and account information | | `RmtInf` | Remittance information | | `Purp` or `CtgyPurp` | Payment purpose or category purpose when applicable | ## Settlement currency The `Ccy` attribute on `IntrBkSttlmAmt` in your submitted message determines the payout currency and flows through to `amounts.debit.currency` and `amounts.credit.currency` in the transaction model. ```xml theme={null} 4800.00 ``` | Currency | Status | | :------- | :---------- | | `USD` | Supported | | `EUR` | Coming soon | | `GBP` | Coming soon | | `AUD` | Coming soon | ## Practical expectations When you prepare a message for Meridian: * send a complete `pacs.008` message document, not a fragment * preserve the schema namespace that matches your chosen `pacs.008` version * preserve the original identifiers used for the payment instruction * include exactly one `CdtTrfTxInf` block in each submitted message * keep the XML as the source artifact that you encode and submit * validate the message against your own network and schema rules before encoding If your implementation profile requires additional envelopes or headers outside the `Document` payload, align that packaging with Meridian during onboarding. The ingestion endpoint itself expects the encoded payload to contain the message document you want processed. ## Example skeleton This example shows the shape Meridian expects at a document level. It is intentionally abbreviated and not a complete, schema-valid payment message. ```xml theme={null} 20260407-000123 2026-04-07T20:11:40Z 1 CLRG ABC-12345 E2E-12345 TX-12345 1250.00 2026-04-08 Sender Name AAAAUS33XXX BBBBGB22XXX Receiver Name Invoice 100045 ``` ## Before you submit Before calling `POST /v1/payout-origination/pacs008`: 1. Build or receive the complete `pacs.008` message document. 2. Validate the XML and business content in your own system. 3. Base64-encode that message document. 4. Send the encoded payload to Meridian with HMAC authentication headers and a separate `Idempotency-Key` header. ## Transaction model mapping Use this section to understand how Meridian maps a submitted `pacs.008` message into the internal transaction model that `GET /v1/transactions` and `GET /v1/transactions/{id}` expose. The read APIs return Meridian's internal transaction model, not the original XML. Each submitted message must contain exactly one `CdtTrfTxInf` block. ### Mapping table | Transaction field | pacs.008 source | Notes | | :------------------------------------------------ | :----------------------------------- | :----------------------------------------------------------------------------------------------- | | `messageId` | `GrpHdr/MsgId` | Meridian uses the message identifier from the submitted `pacs.008` as the message correlation ID | | `externalId` | `CdtTrfTxInf/PmtId/TxId` | This is the primary business reference exposed as `externalId` | | `amounts.debit.amount` | `CdtTrfTxInf/IntrBkSttlmAmt` | Uses the numeric value from the interbank settlement amount | | `amounts.debit.currency` | `CdtTrfTxInf/IntrBkSttlmAmt/@Ccy` | Uses the currency attribute from the interbank settlement amount | | `amounts.credit.amount` | `CdtTrfTxInf/IntrBkSttlmAmt` | Currently mirrors the same settlement amount | | `amounts.credit.currency` | `CdtTrfTxInf/IntrBkSttlmAmt/@Ccy` | Currently mirrors the same settlement currency | | `debitInstrument.originator.displayName` | `CdtTrfTxInf/Dbtr/Nm` | Debtor name | | `debitInstrument.originator.accountNumber` | `CdtTrfTxInf/DbtrAcct/...` | Use the debtor account identifier Meridian receives in the message | | `creditInstrument.beneficiaryAccount.displayName` | `CdtTrfTxInf/Cdtr/Nm` | Creditor name | | `creditInstrument.beneficiaryAccount.id` | `CdtTrfTxInf/CdtrAcct/...` | Use the creditor account identifier Meridian receives in the message | | `creditInstrument.beneficiaryAccount.currency` | `CdtTrfTxInf/IntrBkSttlmAmt/@Ccy` | The current documented example uses the settlement currency | | `source.type` | Internal value `external_submission` | Current documented behavior for pacs.008-ingested transactions | ### Internal fields These transaction fields are internal Meridian fields and are not sourced directly from the `pacs.008` payload: * `status` * `statusReason` * `createdAt` * `updatedAt` * `relatedTransactionId` ### Example mapping Given this `pacs.008` fragment: ```xml theme={null} 20260407-000123 ABC-12345 E2E-12345 TX-12345 1250.00 Sender Name 123456789 Receiver Name 987654321 ``` The transaction model can look like this: ```json theme={null} { "id": "txn_01JVY8Y4N9X2M6S5Q7T1", "messageId": "20260407-000123", "source": { "type": "external_submission", "externalSubmission": { "id": "sub_01JVY8Y4N9X2M6S5Q7T1", "displayName": "Customer message submission" } }, "type": "transfer", "status": "PENDING", "statusReason": null, "externalId": "TX-12345", "relatedTransactionId": null, "amounts": { "debit": { "amount": "1250.00", "currency": "USD" }, "credit": { "amount": "1250.00", "currency": "USD" }, "fees": [], "exchangeRate": "1.0000" }, "debitInstrument": { "type": "ORIGINATOR", "originator": { "displayName": "Sender Name", "accountNumber": "123456789" } }, "creditInstrument": { "type": "BENEFICIARY_ACCOUNT", "beneficiaryAccount": { "id": "987654321", "displayName": "Receiver Name", "currency": "USD" } } } ``` ## Related pages * [Overview](/products/payout-origination/overview) * [Base64 payload encoding](/products/payout-origination/base64-payload-encoding) * [Authentication](/products/payout-origination/authentication) # Request a Sandbox Source: https://docs.mnai.com/products/payout-origination/sandbox Request Sandbox credentials and set up a test environment to build and validate your Meridian Payout Origination integration before going live. ## Requesting Sandbox credentials Meridian provisions Sandbox access on request. To get started, contact your Solutions Engineer and provide the following: * Your organization's name and country of operation * A technical point of contact with name and email * The webhook URL you want Meridian to use for payout origination processing status events * The sender or program identifiers Meridian should associate with your Sandbox traffic * Any IP addresses Meridian should expect if your network policy requires allowlisting Once Meridian receives this information, it provisions your Sandbox environment and delivers your credentials securely. ## What you'll receive Upon provisioning, Meridian securely provides: * **API Key** — used in the `X-Meridian-Api-Key` header on all Payout Origination API requests * **API Secret** — used to compute the HMAC SHA-256 request signature * **Webhook API Key and Secret** — used exclusively for authenticating inbound webhook events * **Sandbox base URL** — for the Payout Origination API These credentials are specific to your Sandbox environment. Meridian issues production credentials separately as part of the go-live process. ## What to validate in Sandbox Before requesting production access, validate that your integration can: * Base64-encode the full source payload without changing its contents * Use the correct `{format}` path slug for the payload you submit * Sign requests with the correct HMAC headers * Submit a message to `POST /v1/payout-origination/{format}` successfully * Store the returned `messageId`, then reconcile the resulting transaction in your own system * Accept and verify webhook notifications for transaction processing status changes * Look up transactions with `GET /v1/transactions?messageId=...` or `GET /v1/transactions/{id}` # Supported formats Source: https://docs.mnai.com/products/payout-origination/supported-formats See which message formats the Payout Origination API accepts and which path slug to use for each one. Use this page to choose the correct `{format}` path slug before you Base64-encode and submit a payout request. Meridian currently accepts these payout origination formats: | Path slug | Source payload | Use when | | :-------- | :---------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------- | | `pacs008` | ISO 20022 `pacs.008.*` XML document | You originate payouts from an existing ISO 20022 payment message | | `json` | JSON object | You want to submit a simpler structured payload where `entityId`, `beneficiaryId`, `amount`, `currency`, and `reference` are all required | ## Common intake rules These rules apply to every supported format: * Base64-encode the full source payload and send it as the raw `text/plain` request body. * Sign the request with Meridian HMAC headers. * Send `Idempotency-Key` as a separate request header. * Use `POST /v1/payout-origination/{format}` with the correct path slug for your message type. * Preserve the exact payload bytes you want Meridian to process before you Base64-encode them. ## Format-specific guides * Use [pacs.008 format](/products/payout-origination/pacs008-format) for ISO 20022 XML submissions. * Use [JSON format](/products/payout-origination/json-format) for Base64-encoded JSON submissions. ## Choosing a format Choose `pacs008` when your upstream system already produces a complete `pacs.008.*` payment message and you want Meridian to ingest that original XML. Choose `json` when you do not have an ISO 20022 payload and want to originate the payout from a minimal JSON document where all of these fields and values are required: * `entityId` * `beneficiaryId` * `amount` * `currency` * `reference` ## Related pages * [Overview](/products/payout-origination/overview) * [Base64 payload encoding](/products/payout-origination/base64-payload-encoding) * [Authentication](/products/payout-origination/authentication) # Overview Source: https://docs.mnai.com/products/payout-origination/webhooks-overview Learn how Meridian delivers webhook notifications, including security, retries, and delivery behavior. ## Configuration Meridian configures webhook endpoints during provisioning. You can configure a separate URL for each webhook event type. If your integration needs different handling per event, set each webhook URL to the appropriate handler endpoint. ## Event routing Use the `X-Meridian-Resource-Type` and `X-Meridian-Event-Type` headers to identify the webhook before you parse the body. These headers tell you which resource the event belongs to and which schema to use. | Header | Example values | Use | | :------------------------- | :-------------------------- | :-------------------------------------------------- | | `X-Meridian-Resource-Type` | `transaction` | Routes the payload to the correct resource handler | | `X-Meridian-Event-Type` | `processing_status_updated` | Selects the specific event parser for that resource | Read these headers first, then deserialize the request body with the matching webhook schema. This lets your handler reject unexpected combinations early and keeps your routing logic aligned with the payload shape. ## Security Webhook requests use HMAC SHA-256 with a dedicated Webhook API Key and Secret. Each request includes the following headers: | Header | Description | | :--------------------- | :------------------------------------------------------------- | | `X-Meridian-Api-Key` | Issued by Meridian for webhook delivery | | `X-Meridian-Timestamp` | Current time in milliseconds, within 60 seconds of the request | | `X-Meridian-Signature` | HMAC SHA-256 signature | Validate the signature on every inbound request before processing the payload. Use the same canonical string construction described in [Authentication](/products/payout-origination/authentication#constructing-the-signature). ## Delivery and retries Meridian considers a webhook delivered when your endpoint returns an HTTP `2xx` response within **30 seconds**. If the request times out or returns a non-`2xx` response, Meridian retries using exponential backoff, up to **6 attempts over 24 hours**. Because retries can produce duplicate deliveries, your webhook handler should be idempotent. Treat every webhook as potentially arriving out of order. Store the status and timestamp you already know for each transaction. Only overwrite when the incoming payload reflects a newer state or a valid progression. Log non-`2xx` responses and return as fast as possible; even short delays may trigger retries that redeliver the same event. # Processing Status Updated Source: https://docs.mnai.com/products/payout-origination/webhooks-processing-status-updated products/payout-origination/api-reference/payout-origination.json webhook processing_status_updated Learn when the processing status updated webhook is sent and what data it includes. # Introduction Source: https://docs.mnai.com/products/retail-va/api-reference/index API reference for creating authenticated Virtual Accounts sessions, including base URLs, credentials, and authentication requirements. All API calls must be made from your backend. Client applications must never call Meridian APIs directly. ## Base URLs | Environment | URL | Purpose | | :---------- | :----------------------------- | :------------------------ | | Sandbox | `https://sandbox.api.mnai.com` | Testing and certification | | Production | `https://api.mnai.com` | Live traffic | Meridian provides your credentials and endpoint access for the Sandbox environment during onboarding. Always validate your integration against Sandbox before requesting production access. ## Example credentials | Credential | Sandbox | Production | | ------------------ | ------------------------------------ | ------------------------------------ | | API Key | `akey_test_q5zemhdwub8ztw2mlpykvhu0` | `akey_live_q5zemhdwub8ztw2mlpykvhu0` | | API Secret | `asec_test_t44ne6ipaneugxurs13ly55t` | `asec_live_t44ne6ipaneugxurs13ly55t` | | Webhook API Key | `wkey_test_z212127xb84fntpw4whvhd9x` | `wkey_live_z212127xb84fntpw4whvhd9x` | | Webhook API Secret | `wsec_test_tolihur9tjlbah7558uuojf3` | `wsec_live_tolihur9tjlbah7558uuojf3` | # Create a Session Source: https://docs.mnai.com/products/retail-va/api-reference/sessions/create-session products/retail-va/api-reference/retail-rtd.json POST /v1/virtual-accounts/sessions Create a Virtual Account Session to launch the hosted Meridian onboarding experience. # Delete a virtual account application Source: https://docs.mnai.com/products/retail-va/api-reference/simulations/delete-virtual-account-application products/retail-va/api-reference/retail-rtd.json DELETE /v1/remittances/users/{userId}/virtual-account-applications/{virtualAccountApplicationId} Delete a user's onboarding application so onboarding can be re-tested in the sandbox. **Sandbox only.** This endpoint is available exclusively in the sandbox environment to support testing. It is not possible to delete a virtual account application in production. Use this in the sandbox to reset a user's onboarding so it can be re-tested. After the application is deleted, the user can submit a new one and go through onboarding again. # Virtual Account Transaction Source: https://docs.mnai.com/products/retail-va/api-reference/webhooks/webhooks-transaction products/retail-va/api-reference/retail-rtd.json webhook virtual_account_transaction Learn when the virtual account transaction webhook is sent and what data it includes. # Create a quote Source: https://docs.mnai.com/products/retail-va/api-reference/withdrawals/create-quote products/retail-va/api-reference/retail-rtd.json POST /v1/users/{userId}/quotes Create a currency conversion quote that locks an exchange rate before initiating a withdrawal. **Legacy endpoint.** This is part of the legacy withdrawal flow and will be deprecated. An upcoming **Transactions Intent API** will replace it. Avoid building new integrations against this endpoint — contact your Solutions Engineer for migration guidance. # Create a withdrawal Source: https://docs.mnai.com/products/retail-va/api-reference/withdrawals/create-withdrawal products/retail-va/api-reference/retail-rtd.json POST /v1/users/{userId}/virtual-accounts/{accountId}/withdrawals Execute a withdrawal from a virtual account using a previously created quote. **Legacy endpoint.** This is part of the legacy withdrawal flow and will be deprecated. An upcoming **Transactions Intent API** will replace it. Avoid building new integrations against this endpoint — contact your Solutions Engineer for migration guidance. # Authentication Source: https://docs.mnai.com/products/retail-va/authentication Secure your API requests using HMAC SHA-256 signatures with pre-shared API keys, including step-by-step instructions and code examples. * All API calls use an HMAC authentication strategy with pre-shared API and secret keys. * Sandbox and production environments have different API credentials. Do not reuse across environments. * All requests must use HTTPS (TLS 1.2 or higher). * You must allow outbound connections to the [Meridian Base URLs](/products/retail-va/api-reference#base-urls). * IP whitelisting may be required for production access depending on your program configuration. Meridian provides details if applicable. Every request must include the following headers: | Header | Description | | :--------------------- | :---------------------------------------------------------------------- | | `X-Meridian-Api-Key` | Issued by Meridian | | `X-Meridian-Timestamp` | Current time in milliseconds (must be within 10 seconds of the request) | | `X-Meridian-Signature` | HMAC SHA-256 signature | Meridian issues your API Key and Secret during Sandbox and production provisioning. When Meridian receives the request, it validates the signature. If the signature does not match, the API immediately returns a `401 Unauthorized` response. ## Constructing the signature Compute the signature as an HMAC SHA-256 hash over a canonical string that concatenates the following values in order: | Signature component | Notes | | ------------------- | --------------------------------------------------------------------------------------------------------------- | | `API-Key` | Same value as `X-Meridian-Api-Key` | | `Timestamp` | Same value as `X-Meridian-Timestamp` | | `Request method` | Example: `GET`, `POST`, `PATCH` | | `Request path` | Including any parameters. For example:
`/v1/users/ususr-s8f9ds7f8sdf789d7fsd89f` | | `Body contents` | The full stringified JSON contents of the request body.
If there is no body, this can be an empty string. |
Compute the HMAC using the language-appropriate crypto library with the secret key. Make sure you compute the HMAC with the API Secret that Meridian issued to you. Hash the final value using the SHA-256 algorithm and set it as the `X-Meridian-Signature` header. ```kotlin kotlin theme={null} import javax.crypto.Mac import javax.crypto.spec.SecretKeySpec fun buildMeridianSignature(apiKey: String, apiSecret: String, method: String, path: String, body: String = ""): Map { val timestamp = System.currentTimeMillis().toString() val canonical = apiKey + timestamp + method + path + body val mac = Mac.getInstance("HmacSHA256").apply { init(SecretKeySpec(apiSecret.toByteArray(), "HmacSHA256")) } val signature = mac.doFinal(canonical.toByteArray()) .joinToString("") { "%02x".format(it) } return mapOf( "X-Meridian-Api-Key" to apiKey, "X-Meridian-Timestamp" to timestamp, "X-Meridian-Signature" to signature, "Content-Type" to "application/json" ) } ``` ```swift swift theme={null} import CryptoKit import Foundation func buildMeridianSignature(apiKey: String, apiSecret: String, method: String, path: String, body: String = "") -> [String: String] { let timestamp = String(Int(Date().timeIntervalSince1970 * 1000)) let canonical = apiKey + timestamp + method + path + body let key = SymmetricKey(data: Data(apiSecret.utf8)) let signature = HMAC.authenticationCode(for: Data(canonical.utf8), using: key) .map { String(format: "%02x", $0) }.joined() return [ "X-Meridian-Api-Key": apiKey, "X-Meridian-Timestamp": timestamp, "X-Meridian-Signature": signature, "Content-Type": "application/json" ] } ``` ```javascript postman theme={null} // ─── Config ────────────────────────────────────────────────────────────────── // Set these as collection variables (never hardcode secrets) const apiKey = pm.variables.get("meridianApiKey"); const apiSecret = pm.variables.get("meridianApiSecret"); // ─── Timestamp (milliseconds) ───────────────────────────────────────────────── const timestamp = Date.now().toString(); // ─── Request components ─────────────────────────────────────────────────────── const method = pm.request.method; // e.g. "POST" const url = pm.request.url; const qs = url.getQueryString(); const path = url.getPath() + (qs ? "?" + qs : ""); // e.g. "/v1/virtual-accounts/sessions" const body = pm.request.body?.raw ?? ""; // ─── Canonical string ───────────────────────────────────────────────────────── const canonical = apiKey + timestamp + method + path + body; // ─── HMAC-SHA256 signature ──────────────────────────────────────────────────── const signature = CryptoJS.HmacSHA256(canonical, apiSecret).toString(CryptoJS.enc.Hex); // ─── Inject into request headers ───────────────────────────────────────────── pm.variables.set("meridianTimestamp", timestamp); pm.variables.set("meridianSignature", signature); // ─── Debug ──────────────────────────────────────────────────────────────────── console.log("Timestamp :", timestamp); console.log("Canonical :", canonical); console.log("Signature :", signature); ```
## Common errors Meridian returns 401 Unauthorized for requests with missing or invalid signatures. The most common causes are: * Canonical string fields concatenated in the wrong order. * Timestamp too far from the server's current time. * Forgetting to compute the HMAC with your unique API Secret. * Forgetting to SHA-256 hash the final string. # Customization Source: https://docs.mnai.com/products/retail-va/customization Configure branding, localization, and customer support settings to tailor the Virtual Accounts experience to your program. ## Branding The hosted UI is intentionally simple and utilitarian, designed to complement your application rather than compete with it. To align the experience with your visual identity, you can configure: * **Primary brand color** — applied to key interactive elements throughout the experience * **Secondary brand color** — used for supporting UI elements and accents * **Company logo** — displayed within the hosted experience to reinforce your brand Provide brand colors as hex values. Submit branding assets and colors to Meridian during the Sandbox provisioning process. ## Localization The Virtual Accounts UI automatically selects the display language based on your customer's device or browser settings. You do not need any additional configuration on your end. Supported languages vary by program. During onboarding, confirm with your Solutions Engineer which languages are available for your program. Ensure that any customer-facing messaging in your application aligns with what the hosted experience supports. ## Customer support center You can configure a custom support entry point within the hosted experience, giving customers a way to access help without leaving the Virtual Accounts flow. The support center can be configured as either: * **A deep link** — routes the customer back into your mobile application to a specific support screen * **A browser URL** — opens an external support page When configured, the hosted UI surfaces the support entry point directly. Contact your Solutions Engineer to set or update your support center URL. # Deposits Source: https://docs.mnai.com/products/retail-va/deposits Learn how customers receive ACH and wire deposits into their Virtual Account, including the transaction lifecycle and Auto-sweep feature. ## How deposits work From the Account Home screen, your customer can access their Virtual Account details — including the account and routing information needed to receive funds. Your customer provides these details to the sending party, who submits the transfer through their own bank via ACH or wire. Once Meridian receives the funds, it credits the deposit to the customer's Virtual Account and sends a [webhook](/products/retail-va/webhooks-overview) notification to your system. ```mermaid theme={null} sequenceDiagram actor Employer participant VA as Customer Virtual Account participant Backend as Partner Backend Employer->>VA: Submits payroll deposit VA->>VA: Balance refreshed in UI session VA->>Backend: Triggers webhook notification ``` ## Deposit methods There are two ways to receive funds into a Virtual Account: | Method | Description | | :------- | :----------------------------------------- | | **ACH** | Standard bank transfer via the ACH network | | **WIRE** | Domestic wire transfer | ## Transaction lifecycle Deposits move through the following statuses: | Status | Description | | :-------- | :-------------------------------------------------------------- | | `PENDING` | Deposit has been initiated and is awaiting settlement | | `SUCCESS` | Funds have been successfully credited to the Virtual Account | | `FAILED` | Deposit could not be completed — see `statusReason` for details | ## Auto-sweep feature When funds arrive in a Virtual Account configured for "Auto-sweep," Meridian immediately moves them to the linked account on your platform. Meridian sets your program defaults during onboarding, and you can manage individual account configurations through Meridian's REST API. ```mermaid theme={null} sequenceDiagram actor Employer participant VA as Customer Virtual Account participant LA as Customer Local Account Employer->>VA: Submits payroll deposit VA->>VA: Calculate Conversion from USD to Local currency VA->>LA: Executes domestic transfer in local currency ``` # Retail Virtual Accounts Source: https://docs.mnai.com/products/retail-va/index Embed Meridian Virtual Accounts in your app so users can generate U.S. bank account details and receive transfers, including payroll deposits. ## Customer and program eligibility The Retail Virtual Accounts product is available to **individual customers within supported jurisdictions**, subject to Meridian's compliance and risk requirements. Beyond the customer information you provide, Meridian collects further details within the hosted experience to determine eligibility. This includes: * Employment and source-of-funds information * Government-issued photo ID * A live selfie for identity verification Meridian performs these checks as part of the onboarding flow. Customers must complete them to open and maintain a virtual account. Customers **must not hold a U.S. Social Security Number (SSN)** to be eligible for the Virtual Accounts product. ## Embedded web experience The Virtual Accounts product is a **web-based experience** designed to be **embedded inside your mobile application using a WebView**. This lets you integrate the full onboarding and account management flow directly into your app while maintaining a consistent, branded experience. The Virtual Accounts flow requires **camera access** for identity verification, including photo ID capture and selfie checks. Configure the embedded WebView to support camera permissions, cookies, local storage, JavaScript, and standard browser APIs. ## Money movement Meridian notifies you when funds arrive in the Virtual Account and can move them to your platform through an integration with your systems. You can configure each account to "Auto-sweep" funds immediately to the linked account on your platform. Alternatively, let your customers manage their balance and decide when to transfer funds. **To perform money movement, Meridian needs the ability to instruct fund transfers on your platform.** Your Solutions Engineer will request documentation on your [Credit API](/products/retail-va/withdrawals#credit-api-requirements). Meridian configures your Auto-sweep preference during the onboarding process. # Information we collect Source: https://docs.mnai.com/products/retail-va/kyc-information The customer information collected during Retail Virtual Account onboarding for KYC. When you onboard a customer into a Retail Virtual Account, Meridian collects the information below for Know Your Customer (KYC) verification. **This list can change.** The fields collected are driven dynamically by the session API and may be added to, removed, or adjusted over time as compliance requirements evolve — so this page may not always reflect the exact current set. For the authoritative, up-to-date list, refer to the [Create a Session](/products/retail-va/api-reference/sessions/create-session) API reference. ## Customer information | Field | Required | Notes | | :---------------------- | :------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | First name | Yes | | | Last name | Yes | | | Email | Yes | | | Phone number | Yes | International format (e.g. `+1234567890`) | | Date of birth | No | | | Address line 1 | No | | | Address line 2 | No | | | City | No | | | State / Province | No | | | Postal code | No | | | Country code | No | ISO country code (e.g. `US`) | | Employment status | No | One of `EMPLOYEE`, `SELF-EMPLOYED`, `RETIRED`, `UNEMPLOYED`, `OTHER` (`SELF_EMPLOYED` also accepted) | | Employment description | No | Free-text description of employment | | Occupation | No | | | Primary source of funds | No | One of `EMPLOYMENT`, `SAVINGS`, `WINNINGS`, `MARITAL`, `REAL_ESTATE`, `TRUST`, `INVESTMENT`, `COMPANY`, `COMPANY_CAPITAL`, `LOAN`, `PRIVATE_CAPITAL`, `GRANT`, `OTHER` | | Source description | No | Free-text description of the funding source | | Monthly deposit volume | No | Expected monthly deposit amount | | Deposit currency | No | ISO currency code (e.g. `USD`) | # Request a Sandbox Source: https://docs.mnai.com/products/retail-va/sandbox Request Sandbox credentials and set up a test environment to build and validate your Meridian integration before going live. ## Requesting Sandbox credentials Meridian provisions Sandbox access on request. To get started, contact your Solutions Engineer and provide the following: * Your organization's name and country of operation * A technical point of contact (name and email) * The webhook URL you'd like Meridian to deliver transaction events to * The customer support site URL for your tech support team (if you have one) * The Credit API endpoint details for any withdrawal requests * Your company logo * Your primary and secondary brand colors (hex values preferred) * Your preference for whether Meridian should Auto-sweep USD funds from Virtual Accounts to local linked accounts Once Meridian receives this information, it provisions your Sandbox environment — including your branded UI configuration — and delivers your credentials securely. ## What you'll receive Upon provisioning, Meridian securely provides: * **API Key** — used in the `X-Meridian-Api-Key` header on all session API requests * **API Secret** — used to compute the HMAC SHA-256 request signature * **Webhook API Key and Secret** — a separate key pair used exclusively for authenticating inbound webhook events These credentials are specific to your Sandbox environment. Meridian issues production credentials separately as part of the go-live process. # Session management Source: https://docs.mnai.com/products/retail-va/session-management Create and manage authenticated sessions that launch Meridian's hosted Virtual Accounts UI inside your application's WebView. ## Create a new UI Session Each session represents a single, self-contained interaction. Your customer completes onboarding or account management actions within the embedded experience and exits when finished. Because the experience runs in a WebView, your application must provide a way for your customer to leave the flow. This is typically a **custom navigation bar or close control** in the host application. Each time your customer re-enters the Virtual Accounts experience, you must create a **new authentication session** and launch a new embedded experience. This ensures that access is explicit, time-bound, and scoped to a single interaction. Your application must follow the sequence below each time a customer enters the Virtual Accounts experience: Your backend calls the Virtual Accounts API to [create a new session](/products/retail-va/api-reference/sessions/create-session) and provide user details in the `customer` object with a unique `customerId`. The Virtual Accounts API returns a `sessionKey` that you use to generate a fully formed `sessionURL`. Pass this URL to the client without modification. ```jsx Sandbox theme={null} sessionURL = `https://sandbox.va.meridianpay.com?sessionKey=${sessionKey}` ``` ```jsx Production theme={null} sessionURL = `https://va.meridianpay.com?sessionKey=${sessionKey}` ``` The client loads the `sessionURL` directly inside a [WebView](/products/retail-va/web-view) that meets the required configuration and permission requirements. Your customer completes onboarding or account actions and exits the experience using the host application's navigation controls. ```mermaid theme={null} sequenceDiagram actor Customer participant App as Partner Mobile App participant Backend as Partner Backend participant API as Meridian Backend participant UI as Meridian UI Session Customer->>App: Taps "Open Virtual Account (VA)" App->>Backend: Create a new VA UI Session Backend->>API: POST /v1/virtual-accounts/sessions API-->>Backend: 201 Created Backend-->>App: Return the sessionKey Note over App,UI: Session is single-use · expires in 30 min · must not be cached or reused App->>UI: Generate and Load the sessionURL in a WebView
(camera · cookies · localStorage · JS enabled) ```
## UI Session lifecycle Virtual Accounts sessions follow these rules: * Sessions are **single-use** and must not be reused. * Sessions are **short-lived** and expire after **30 minutes**. * Sessions are **payload-bound** to the customer identifier and any onboarding or receiver details submitted at creation time. * Any change to customer identifiers, KYC information, or receiver details requires creating a **new session**. * Session URLs must **not** be cached or stored for later use. These constraints ensure data freshness, deterministic behavior, and secure access to the hosted experience. # Simulate KYC outcomes Source: https://docs.mnai.com/products/retail-va/simulate-kyc Use sandbox test inputs to simulate different KYC application outcomes during development and testing. By default, all KYC applications in the sandbox are automatically approved. Use the inputs below to simulate specific outcomes during testing. | Outcome | How to trigger | | :--------------------------------- | :----------------------------------------------------------- | | **Approved** (default) | Submit any application normally — no special input required | | **Document verification declined** | Set the customer's first name to `Decline` (case-sensitive) | | **EDD Required** | Set occupation type to `Other` with the value `EDD Required` | When **EDD Required** is triggered, the application pauses and must be manually resolved by a Meridian ops user before onboarding can continue. See [Handling onboarding exceptions](/products/retail-va/web-view#handling-onboarding-exceptions) for details on how to handle this status in your integration. ## Resetting a test user In sandbox, you can delete a user's virtual account application to reset their onboarding state. This lets you run the same user through onboarding repeatedly in automated test scripts without needing to create a new user each time. Use the [Delete a virtual account application](/products/retail-va/api-reference/simulations/delete-virtual-account-application) endpoint to delete the application, then submit a new one to go through onboarding again from the beginning. This endpoint is only available in sandbox. Virtual account applications cannot be deleted in production. # WebView Source: https://docs.mnai.com/products/retail-va/web-view The Virtual Accounts experience must be loaded in a WebView environment that supports all required capabilities for onboarding and account management. ## Requirements The WebView must be configured with: * Camera access enabled for photo ID capture and selfie checks * Cookies enabled to maintain session state * Local storage enabled * JavaScript enabled * Standard browser APIs available Unsupported or restricted WebView configurations — such as incognito mode, disabled storage, or camera-disabled environments — can prevent your customer from completing onboarding or cause unrecoverable flows. Verify that your WebView implementation explicitly grants camera permissions and supports persistent storage before launching the Virtual Accounts experience. Once you generate a [Session URL](/products/retail-va/session-management), you can initialize your WebView for either iOS or Android. ```kotlin kotlin theme={null} import android.webkit.PermissionRequest import android.webkit.WebChromeClient import android.webkit.WebView import android.webkit.WebViewClient fun loadVirtualAccountsSession(webView: WebView, sessionKey: String) { webView.settings.apply { javaScriptEnabled = true domStorageEnabled = true // localStorage allowFileAccess = true mediaPlaybackRequiresUserGesture = false } // Persist cookies across sessions android.webkit.CookieManager.getInstance().setAcceptCookie(true) android.webkit.CookieManager.getInstance().setAcceptThirdPartyCookies(webView, true) webView.webViewClient = WebViewClient() // Required to grant camera access for photo ID capture and selfie webView.webChromeClient = object : WebChromeClient() { override fun onPermissionRequest(request: PermissionRequest) { request.grant(request.resources) } } webView.loadUrl("https://va.meridianpay.com?sessionKey=$sessionKey") } ``` ```swift swift theme={null} import WebKit class VirtualAccountsViewController: UIViewController, WKUIDelegate { var webView: WKWebView! override func loadView() { let config = WKWebViewConfiguration() config.allowsInlineMediaPlayback = true config.mediaTypesRequiringUserActionForPlayback = [] // Enable camera access for photo ID capture and selfie config.preferences.javaScriptEnabled = true config.websiteDataStore = WKWebsiteDataStore.default() // persistent cookies & localStorage webView = WKWebView(frame: .zero, configuration: config) webView.uiDelegate = self view = webView } func loadSession(sessionKey: String) { let url = URL(string: "https://va.meridianpay.com?sessionKey=\(sessionKey)")! webView.load(URLRequest(url: url)) } // Required to allow camera permission prompts inside the WebView func webView(_ webView: WKWebView, requestMediaCapturePermissionFor origin: WKSecurityOrigin, initiatedByFrame frame: WKFrameInfo, type: WKMediaCaptureType, decisionHandler: @escaping (WKPermissionDecision) -> Void) { decisionHandler(.grant) } } ``` ## User experience The Meridian UI Session provides two initial experiences at start-up: ### 1. KYC onboarding When your customer opens the Virtual Accounts experience for the first time, Meridian detects that no approved KYC application exists and presents the onboarding screens. The flow guides them through providing employment and source-of-funds details, uploading a government-issued photo ID, and completing a live selfie check. Once verified, the account opens and your customer can begin transacting. #### Handling onboarding exceptions Sometimes, your customer must submit additional documentation to open an account. In this scenario, the application has a status of `EDD_REQUIRED`. You and the Meridian team collect the additional documentation outside of the product flow. Meridian may reject a virtual account application for the following reasons: * `BANK_REJECTION` - The bank or banking provider rejected the application. Your customer must reach out to support to resolve the issue. * `RISK_REJECTION` - Your customer failed Meridian's risk assessment. This can happen for many reasons, so your customer must contact support to resolve the issue. * `INTERNAL_ERROR` - An unexpected error occurred while processing the application. Your customer may resubmit, but verify whether the account was still created before allowing a new submission. ### 2. Account home For returning customers with an approved KYC application on record, Meridian routes them directly to the Account Home screen. From here your customer can review their transaction history and initiate new deposits or withdrawals. You do not need to do anything beyond the standard session creation flow. # Webhooks Source: https://docs.mnai.com/products/retail-va/webhooks-overview Learn how Meridian delivers real-time event notifications to your system via HTTP POST webhooks, including configuration, security, and retry behavior. ## Configuration Meridian configures webhook endpoints during provisioning. You can register separate URLs for Sandbox and production — Meridian delivers all events for a given environment to a single endpoint. If you need to route events differently, your handler must direct them based on the event headers. ## Event routing Use the `X-Meridian-Resource-Type` and `X-Meridian-Event-Type` headers to identify the webhook before you parse the body. These headers tell you which resource the event belongs to and which specific event occurred. | Header | Example values | Use | | :------------------------- | :------------------------------ | :------------------------------------------------- | | `X-Meridian-Resource-Type` | `virtual_account_transaction` | Routes the payload to the correct resource handler | | `X-Meridian-Event-Type` | See the event types table below | Identifies the specific event for that resource | | Resource | Event types | | :---------------------------- | :--------------------------------------------------------------------------- | | `virtual_account_transaction` | `virtual_account_transaction_created`, `virtual_account_transaction_updated` | Both transaction events carry the same [Virtual Account Transaction](/products/retail-va/api-reference/webhooks/webhooks-transaction) payload: `created` fires when a new transaction is initiated, and `updated` fires when the status of an existing transaction changes. Read the headers first, then deserialize the body with the matching webhook schema. ## Security Webhook requests use the same HMAC SHA-256 scheme as the REST API, but with a **dedicated Webhook API Key and Secret** issued separately from your REST API credentials. Each request includes the following headers: | Header | Description | | :--------------------- | :------------------------------------------------------------------- | | `X-Meridian-Api-Key` | Issued by Meridian, specifically for Webhooks | | `X-Meridian-Timestamp` | Current time in milliseconds
(within 60 seconds of the request) | | `X-Meridian-Signature` | HMAC SHA-256 signature | Validate the signature on every inbound request before processing the payload. Use the same canonical string construction described in [Authentication](/products/retail-va/authentication#constructing-the-signature). ## Delivery and retries Meridian considers a webhook delivered when your endpoint returns an HTTP `2xx` response within **30 seconds**. If the request times out or returns a non-`2xx` status, Meridian retries using exponential backoff — up to **6 attempts over 24 hours**. Because retries can result in duplicate deliveries, your webhook handler should be **idempotent** — processing the same event more than once should produce the same outcome. # Withdrawals Source: https://docs.mnai.com/products/retail-va/withdrawals Learn how customers withdraw funds from their Virtual Account and receive local currency payouts through your Credit API. ## How withdrawals work From the Account Home screen, your customer selects Withdrawal and enters the amount of USD to convert. Meridian deducts the amount from the Virtual Account balance and marks the transaction as pending. Meridian then sends a payout request to your designated Credit API. Your Credit API transfers the funds in local currency to your customer's local bank account. Once the transfer is confirmed, the Virtual Account balance updates to reflect the settled status. ```mermaid theme={null} sequenceDiagram actor Customer participant UI as Meridian UI Session participant Backend as Partner Backend Customer->>UI: Customer requests a withdrawal UI->>UI: Refresh balance with Pending status UI->>Backend: POST a request to the Credit API Backend->>Backend: Transfers local currency into customer's local account Backend-->>UI: Returns confirmation response UI->>UI: Refresh balance with Settled status ``` ## How withdrawals are funded Meridian fulfills withdrawals through the bank partner's Credit API. During onboarding, Meridian's Implementation team integrates with that API to push funds from Meridian's sponsored account to the customer's account at the home bank. For full details on how the Credit API works and what the integration requires, see the [Credit API](/products/bank-transfers/credit-api) page.