# Inyo Global Developer Documentation β€” Full Content > Complete documentation optimized for LLM consumption. # πŸ‘‹ Welcome to the Inyo API Docs Welcome to the Inyo Global Developer Portal β€” your gateway to building powerful, secure, and scalable payment solutions. Our suite of APIs empowers developers to integrate a wide range of financial services into their applications, from cross-border payments and currency exchange to card tokenization, 3DS authentication, and real-time transaction tracking. Whether you’re a fintech startup, e-commerce platform, or enterprise solution, our APIs are designed to be developer-friendly, compliant, and built for performance. ### 🌟 What You Can Do with Inyo Global APIs * Receive and manage payments (AFT , OCT, P2P, ACH, card) with dynamic routing and fraud control * Send domestic & international payouts through our proprietary network spanning 150+ countries * Automate KYC, AML, and compliance workflows, including user onboarding, identity verification, and real-time monitoring * Tokenize and manage cards securely, with PCI-grade protection * Support multi-currency conversions and settlement, helping you operate globally with confidence * Trigger and handle 3D Secure (3DS) authentication for added transactional safety * Track, reconcile, and report transactions in real time * Hook into our webhooks system for instant event-driven updates on payment and compliance events ### 🧰 Tools & Libraries * RESTful API endpoints * JSON-based payloads * SDKs and code samples (coming soon) * Pre-configured support for major 3DS providers and acquiring networks ### πŸ“˜ Need Help? * Visit our Authentication Guide to learn how to securely access our APIs * Check out Common Use Cases for step-by-step implementation examples * Reach out to our sales team for assistance # Our products We offer two powerful payment solutions tailored to different operational needs: Payments Gateway and Remittances API. Whether you want full control over your payment flows or need a plug-and-play remittance solution with built-in compliance, we’ve got you covered. *** ### πŸ”— Payments Gateway Want to send or receive payments and leverage your own infrastructure? Payments Gateway is for you. Streamlined domestic and international payments for platforms of all sizes Our Payments Gateway API is designed for businesses that want to send or receive payments efficiently β€” domestically or internationally β€” while retaining control over the user experience and compliance policies. #### βœ… Ideal for: * Domestic platforms looking to streamline fund collection and payouts * Marketplaces, SaaS platforms, and service providers managing supplier or user disbursements * Licensed fintechs or financial institutions with their own compliance stack #### πŸš€ Key Features: * Handle domestic and cross-border payments using card (AFT/OCT), bank transfers, and ACH * Leverage our orchestration platform, maximizing anti-fraud controls and different routes for authorization * Tokenize and store cards securely with PCI-compliant infrastructure * Trigger 3D Secure (3DS) authentication for enhanced payment security * Integrate webhooks for payment status updates and reconciliation tools * Maintain control over KYC, fraud, and compliance processes, if desired *** ### 🌍 Remittances API Cross-border payouts with built-in compliance, KYC, and licensing The Remittances API builds on our Gateway infrastructure and adds a full regulatory layer β€” including KYC onboarding, AML screening, and audit-ready transaction controls. It’s the ideal solution for platforms looking to offer international transfers or gig payouts without managing complex compliance requirements. #### βœ… Ideal for: * Platforms without payment licenses * Gig and creator economy tools * Global payroll and disbursement platforms * Remittance startups or apps looking to launch quickly under a compliant framework #### 🧩 What’s Included: * All features of the Payments Gateway, plus: * Built-in KYC/KYB onboarding flows * Real-time AML & sanctions screening * Transaction monitoring and fraud detection * Regulatory reporting and compliance management * Access to Inyo Global’s licensing coverage *** ### πŸ” Choosing the Right API | Regulatory License Required |

No (for domestic use)

Yes (for cross-border)

| No (license and compliance handled by Inyo) | | --------------------------- | -------------------------------------------------------------- | --------------------------------------------------- | | KYC/AML/Compliance |

Bring Your Own
Add compliance as an external module

| Fully built-in | | Domestic Payments | βœ… Yes | βœ… Yes | | International Payments | βœ… Yes (with license) | βœ… Yes (license included) | | Best For | Platforms improving payments or managing their own compliance | Platforms needing turnkey cross-border capabilities | *** #### πŸ’‘ Let’s Get Started Whether you need modern rails for domestic payments or a fully managed remittance stack, Inyo Global makes it easy to build, scale, and stay compliant.
πŸ‘‰ [Explore the docs](https://dev.inyoglobal.com) or [contact us](mailto:support@inyoglobal.com) to learn more.
--- description: >- Welcome to the Inyo Payment Gateway documentation. Accept card and ACH payments, send domestic and international payouts, and manage the full transaction lifecycle. --- # Payments Gateway The Inyo Payment Gateway provides a unified API for **pull** (collect) and **push** (payout) operations across multiple payment methods and providers. ## Capabilities | Feature | Description | |---|---| | **Card Payments** | Accept Visa, Mastercard, Amex, and Discover via tokenized card data | | **ACH Transfers** | Pull funds from US bank accounts | | **International Payouts** | Push to cards, bank accounts, PIX, and wallets in 165+ countries | | **3D Secure** | Built-in 3DS authentication with Cardinal | | **Smart Routing** | Automatic provider selection for optimal success rates | | **Fraud Prevention** | AVS, CVC verification, velocity controls, and built-in anti-fraud | ## Documentation Guide | Section | What You'll Learn | |---|---| | [**Getting Started**](getting-started.md) | Quick start guide with end-to-end payment example | | [**Authentication**](authentication-methods.md) | OAuth 2.0 token flow | | [**Environments**](environments.md) | Sandbox and production URLs | | [**Technical Resources**](technical-resources.md) | API keys, rate limits, OpenAPI spec | | [**APIs**](apis/) | All endpoints: tokenization, payments, capture, void, refund | | [**Webhooks**](apis/webhooks.md) | Real-time transaction notifications | | [**Test Data**](apis/test-data/cards.md) | Sandbox cards and simulation codes | | [**Mitigating Fraud**](mitigating-fraud.md) | Security best practices | | [**Background**](background/) | Architecture and infrastructure overview | --- description: >- Everything you need to accept payments, send payouts, and manage the full transaction lifecycle with the Inyo Payment Gateway. --- # Getting Started ## Overview The Inyo Payment Gateway enables you to: - **Pull funds** β€” Charge customers via credit/debit cards or ACH bank transfers - **Push funds** β€” Send payouts domestically (ACH) or internationally (cards, PIX, wallets, bank accounts) - **Pull + Push** β€” Collect and disburse in a single API call All operations follow a consistent REST API pattern with OAuth 2.0 authentication. ## Prerequisites Before you begin integrating, you'll need three credentials from the Inyo team: | Credential | Format | Purpose | |---|---|---| | **Public Key** | `a23271e1-c1c0-44d3-...` | Card tokenization via the JavaScript library | | **Client ID** | `MY_CLIENT_ID` | OAuth authentication (`/oauth/token`) | | **Client Secret** | `4efa3460-a121-1ca9-...` | OAuth authentication (`/oauth/token`) | > **To obtain credentials**, contact the Inyo commercial team. During onboarding, you'll also provide your origin URL(s) for CORS whitelisting. ## Integration Flow Here's the typical integration path for a card payment: ``` 1. Authenticate β†’ POST /oauth/token 2. Tokenize card β†’ Client-side via inyo.js 3. Create payment β†’ POST /v2/payment 4. Handle 3DS β†’ Redirect to redirectAcsUrl (if CHALLENGE) 5. Capture payment β†’ POST /payments/{id}/capture 6. (Optional) Void β†’ POST /payments/{id}/void 7. (Optional) Refund β†’ POST /payments/{id}/refund ``` For ACH and push payments, skip step 2 (tokenization) and provide bank/destination details directly in the payment payload. ## Quick Example: Authorize a Card Payment ### Step 1 β€” Get an access token ```bash curl -X POST https://sandbox-gw.simpleps.com/oauth/token \ -H 'Content-Type: application/json' \ -d '{ "clientId": "YOUR_CLIENT_ID", "secretId": "YOUR_CLIENT_SECRET" }' ``` **Response:** ```json { "accessToken": "eyJhbGciOiJSUzI1NiIs...", "tokenType": "Bearer", "expiresIn": 3600 } ``` ### Step 2 β€” Tokenize a card (client-side) Load the tokenizer library and create a token from the cardholder's data. See [Tokenizing Cards](apis/tokenizing-cards.md) for the full guide. ```html ``` ```javascript const tokenizer = new InyoTokenizer({ targetId: '#payment-form', publicKey: 'YOUR_PUBLIC_KEY', storeLaterUse: false, successCallback: (response) => { // response.additionalData.token contains the card token submitPayment(response.additionalData.token); }, errorCallback: (error) => { console.error('Tokenization failed:', error.code); } }); // Call when user clicks "Pay" tokenizer.tokenizeCard(); ``` ### Step 3 β€” Create the payment ```bash curl -X POST https://sandbox-gw.simpleps.com/v2/payment \ -H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \ -H 'Content-Type: application/json' \ -d '{ "externalPaymentId": "order-12345", "ipAddress": "203.0.113.42", "paymentType": "PULL", "capture": false, "amount": { "total": 99.99, "currency": "USD" }, "sender": { "firstName": "John", "lastName": "Smith", "address": { "countryCode": "US", "stateCode": "NY", "city": "New York", "line1": "123 Main Street", "zipCode": "10001" }, "paymentMethod": { "type": "CARD", "cardTokenId": "ab5fc589-8b48-4531-94c0-68b0629c13fe" } } }' ``` **Response (3DS Challenge required):** ```json { "status": 200, "data": { "paymentId": "dce568c6-98ec-456c-bb33-4a6809c4fff8", "externalPaymentId": "order-12345", "amount": 99.99, "status": "CHALLENGE", "redirectAcsUrl": "https://sandbox-gw.simpleps.com/secure-code/start-challenge?token=dce568c6-...", "approved": false, "captured": false, "message": "Payment awaiting 3DS challenge verification" } } ``` **Response (Authorized directly):** ```json { "status": 200, "data": { "paymentId": "dce568c6-98ec-456c-bb33-4a6809c4fff8", "externalPaymentId": "order-12345", "amount": 99.99, "status": "AUTHORIZED", "approved": true, "captured": false, "message": "Payment Approved", "responseCode": "00", "cvcResult": "APPROVED", "avsResult": "APPROVED" } } ``` ### Step 4 β€” Capture when ready ```bash curl -X POST https://sandbox-gw.simpleps.com/payments/order-12345/capture \ -H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \ -H 'Content-Type: application/json' ``` ## Payment Lifecycle ``` β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ CHALLENGEβ”‚ ──(3DS fail)──→ DECLINED β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜ β”‚ (3DS success) β–Ό β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Payment │────→│ AUTHORIZED │────→│ CAPTURED │────→ REFUNDED β”‚ Created β”‚ β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ (full/partial) β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β–Ό VOIDED ``` - **CHALLENGE** β†’ Cardholder must complete 3DS verification - **AUTHORIZED** β†’ Funds reserved; you can capture or void - **CAPTURED** β†’ Funds settled; you can refund (full or partial) - **VOIDED** β†’ Authorization cancelled before capture - **DECLINED** β†’ Payment rejected by issuer or fraud rules ## Environments | Environment | API Base URL | Tokenizer URL | |---|---|---| | **Sandbox** | `https://sandbox-gw.simpleps.com` | `https://cdn.simpleps.com/sandbox/inyo.js` | | **Production** | `https://gw.simpleps.com` | `https://cdn.simpleps.com/production/inyo.js` | ## What's Next | Topic | Description | |---|---| | [Authentication](authentication-methods.md) | OAuth 2.0 token details and best practices | | [Tokenizing Cards](apis/tokenizing-cards.md) | Client-side card tokenization with `inyo.js` | | [Pulling Funds](apis/payment/pulling-funds/) | Card and ACH payment collection | | [Push Transactions](apis/payment/push-transaction.md) | Domestic and international payouts | | [3D Secure](apis/payment/pulling-funds/cards/authorizing/handling-3d-secure.md) | Challenge flow and redirect handling | | [Webhooks](apis/webhooks.md) | Real-time transaction status notifications | | [Test Data](apis/test-data/cards.md) | Sandbox test cards and simulation codes | | [Technical Resources](technical-resources.md) | API keys, URLs, rate limits, and OpenAPI spec | # Background # System architecture #### 1. **Microservices Architecture** Inyo gateway is built on a microservices architecture, where each functionality is encapsulated in a standalone service, compiled and tested and deployed automatically. This approach ensures: * Modular development and deployment. * High scalability to handle varying workloads. * Fault isolation, where issues in one service do not affect others. Each microservice is containerized, enabling consistent environments and seamless deployment across different platforms #### 2. **Payment Router Mechanism** A central component of the architecture is the **Payment Router**, which dynamically selects the optimal payment route based on: * **Transaction Type**: Determines whether the operation is a **pull** or **push**. * **Payment Method**: Identifies the payment type (e.g., **ACH**, **Card**, or **different payouts formats**). * **Currency**: Considers the currencies of the origin and destination countries to ensure compatibility and compliance with local regulations. The router ensures that every transaction is processed efficiently and routed to the appropriate provider, optimizing performance and reliability. #### 3. **Provider-Specific Microservices** To cater to the diverse requirements of payment providers, each provider has its own dedicated microservice. These microservices: * Implement the specific rules and standards mandated by their respective providers. * Manage provider-specific APIs, ensuring seamless integration. * Are independently deployable, allowing for quick updates and scaling. #### 4. **Integration with Payment Router** The provider microservices are tightly integrated with the **Payment Router**, making them readily available for the gateway to: * Offer a wide range of payment integrations to client banks. * Dynamically scale as new providers are added, enhancing the system’s flexibility. This tightly coupled design ensures that the gateway remains extensible and capable of supporting future integrations without disrupting existing services. #### 5. **Scalability and Security** The architecture leverages modern design principles to ensure: * **Horizontal Scalability**: Services can scale independently based on load. * **Enhanced Security**: Each microservice operates in an isolated environment, reducing vulnerabilities. This architecture makes Inyo gateway a powerful tool for businesses, enabling seamless financial transactions while maintaining the highest standards of performance and reliability. --- description: >- To ensure secure, reliable, and efficient operations, Inyo gateway leverages the following technologies and services --- # Gateway environment settings #### 1. **Authentication and Security with IdP** Inyo gateway uses **IdP** for authentication and security management. The IdP enables: * Centralized user management. * Support for various authentication protocols (e.g., OAuth2, OpenID Connect). #### 2. **Publish/Subscriber Architecture** Inyo gateway processes transactions using a **publish/subscribe architecture** powered by **Industry Standards**. This mechanism ensures: * Decoupling between services, allowing for independent scaling. * Efficient transaction processing by distributing tasks among multiple consumers. * High reliability through message durability and retry mechanisms. #### 3. **KMS for Data Encryption** To protect sensitive data, Inyo gateway uses **KMS (Key Management Service)** to create and manage encryption keys. These keys are used to securely store: * Card details. * Provider credentials. * Other sensitive information that requires strict security compliance. By integrating these technologies, Inyo gateway achieves a secure and scalable environment for managing financial transactions across diverse providers and payment methods. --- description: >- Inyo Gateway uses OAuth 2.0 Client Credentials to authenticate all API requests. This page explains how to obtain and use access tokens. --- # Authentication ## Overview All Payment Gateway API calls (except tokenization, which uses your public key) require a Bearer token obtained via the `/oauth/token` endpoint. **Key characteristics:** - **Grant type**: Client Credentials (machine-to-machine) - **Token lifetime**: Short-lived (typically 1 hour) β€” request a new token when expired - **Transport**: Always use HTTPS; never send credentials over unencrypted connections - **Storage**: Tokens should be stored securely server-side; never expose them in client-side code ## Obtaining an Access Token ``` POST /oauth/token ``` ### Request ```bash curl -X POST https://sandbox-gw.simpleps.com/oauth/token \ -H 'Content-Type: application/json' \ -d '{ "clientId": "YOUR_CLIENT_ID", "secretId": "YOUR_CLIENT_SECRET" }' ``` | Field | Type | Required | Description | |---|---|---|---| | `clientId` | string | βœ… | Your OAuth client identifier, provided during onboarding | | `secretId` | string | βœ… | Your OAuth client secret, provided during onboarding | ### Response (Success β€” 200) ```json { "accessToken": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...", "tokenType": "Bearer", "expiresIn": 3600 } ``` | Field | Type | Description | |---|---|---| | `accessToken` | string | JWT token to include in subsequent API calls | | `tokenType` | string | Always `"Bearer"` | | `expiresIn` | number | Token validity in seconds | ### Response (Unauthorized β€” 401) ```json { "status": 401, "message": "Unauthorized request", "responseCode": "SE_001" } ``` ## Using the Token Include the access token in the `Authorization` header of every API request: ```bash curl -X POST https://sandbox-gw.simpleps.com/v2/payment \ -H 'Authorization: Bearer eyJhbGciOiJSUzI1NiIs...' \ -H 'Content-Type: application/json' \ -d '{ ... }' ``` ## Token Management Best Practices 1. **Cache tokens** β€” Reuse the same token until it expires rather than requesting a new one per API call. The authentication endpoint has a rate limit of 60 requests/minute. 2. **Handle expiry gracefully** β€” When you receive a `401` or `403` response, request a new token and retry the original request. 3. **Never expose credentials client-side** β€” The `clientId` and `secretId` must only be used from your backend server (Backend-for-Frontend pattern). The only client-side credential is the `publicKey` used for card tokenization. 4. **Rotate secrets periodically** β€” Contact the Inyo team to rotate your client secret if you suspect it has been compromised. ## Authentication vs. Tokenization | Concern | Server-side (OAuth) | Client-side (Tokenizer) | |---|---|---| | **Credential** | `clientId` + `secretId` | `publicKey` | | **Endpoint** | `POST /oauth/token` | `inyo.js` library | | **Purpose** | Authenticate API calls | Tokenize card data | | **Exposure** | Backend only | Browser (safe to expose) | ## Rate Limits | Endpoint | Limit | |---|---| | Authentication (`/oauth/token`) | 60 requests/minute | | FX rates | 200 requests/minute | | All other authenticated endpoints | 400 requests/minute | When rate-limited, the API returns HTTP `429`. Implement exponential backoff with jitter in your retry logic. --- description: >- Inyo provides separate sandbox and production environments. Use sandbox for development and testing with simulated transactions. --- # Environments ## URLs | Resource | Sandbox | Production | |---|---|---| | **API Base** | `https://sandbox-gw.simpleps.com` | `https://gw.simpleps.com` | | **Tokenizer JS** | `https://cdn.simpleps.com/sandbox/inyo.js` | `https://cdn.simpleps.com/production/inyo.js` | | **OpenAPI Spec** | [sandbox-gw.simpleps.com/q/swagger-ui](https://sandbox-gw.simpleps.com/q/swagger-ui/#/) | β€” | ## Sandbox Environment Use the sandbox for development and testing. Transactions are simulated β€” no real money moves and no card networks are contacted. **Sandbox features:** - Full API functionality matching production behavior - [Test cards](apis/test-data/cards.md) for simulating approvals, declines, 3DS challenges, and AVS results - Simulation codes via cardholder name (e.g., `REFUSED` triggers a decline) - Webhook delivery to your configured endpoints **Sandbox limitations:** - No real card network authorization - Some issuer-specific behaviors may differ from production - Rate limits may be lower than production ## Production Environment Use production for live, real-money transactions. Ensure all integrations are thoroughly tested in sandbox before going live. **Before going to production:** - [ ] All payment flows tested end-to-end in sandbox - [ ] Webhook endpoints configured and verified - [ ] Error handling and retry logic implemented - [ ] 3DS redirect flows tested (success and failure) - [ ] CORS origin URLs provided to Inyo team - [ ] Production credentials received and stored securely ## CORS Configuration During onboarding, provide Inyo with your origin URLs for both environments. The tokenizer library (`inyo.js`) communicates with Inyo's servers from the browser, so your domain must be whitelisted per [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) policy. # Technical Resources Prior to start integrating and testing, it's necessary to have three components in hand, that needs to be provided by our team. | Key | Sample value | Used for | | :--- | :--- | :--- | | public key | `a23271e1-c1c0-44d3-4567-5610217d1151` | Tokenizing cards using the javascript library | | client id | `MY_CLIENT_ID` | client id to be used during the oauth authentication | | client secret | `4efa3460-a121-1ca9-abcd-e1345ff5510d` | client secret to be used during the oauth authentication | It's also important to take note of the following resources: | Resource | URL | | :--- | :--- | | Static javascript component | [https://cdn.simpleps.com/production/inyo.js](https://cdn.simpleps.com/production/inyo.js) | | OpenAPI specs | [https://sandbox-gw.simpleps.com/q/swagger-ui](https://sandbox-gw.simpleps.com/q/swagger-ui) | | Resource | Sandbox | Production | | :--- | :--- | :--- | | API endpoint | [https://sandbox-gw.simpleps.com/](https://sandbox-gw.simpleps.com/) | [https://gw.simpleps.com/](https://gw.simpleps.com/) | | Postman | Upon request | | Considerations: During the key generation, it will be required by our team the url of the origin server used for development and production. This origin url will ensure that communications in between the browser and our servers are allowed, per [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) rules. #### Rate Limits Each client is allotted a standard amount of requests per minute, that can be reviewed based on specific scenarios. You must implement techniques such as exponential backoff, and be ready to deal with HTTP 429 responses. | Resource | Limit (Requests/Minute) | | :--- | :--- | | Authentication | 60 | | Rates | 200 | | All other authenticated requests | 400 | --- description: >- Protect your business from fraud and chargebacks. Understand the chargeback process, why anti-fraud matters, and best practices for secure payment operations. --- # Mitigating Fraud & Managing Chargebacks ## Why This Matters Every card transaction carries risk. When fraud slips through or a legitimate customer disputes a charge, the result is a **chargeback** β€” and chargebacks are one of the most expensive problems in payments. Understanding how they work, and how to prevent them, is essential for any business accepting card payments. --- ## What Is a Chargeback? A chargeback is a **forced reversal** of a card transaction, initiated by the cardholder through their issuing bank. Unlike a refund (which you initiate voluntarily), a chargeback is imposed on you β€” the funds are pulled from your account, and you must prove the transaction was legitimate to get them back. ### How the Chargeback Process Works ``` 1. Cardholder disputes a charge with their bank β”‚ β–Ό 2. Issuing bank reviews the claim and files a chargeback β”‚ β–Ό 3. Funds are immediately debited from your merchant account + a chargeback fee is applied (typically $15–$100 per case) β”‚ β–Ό 4. You receive notification and have a limited window to respond (usually 7–30 days depending on the network) β”‚ β–Ό 5. You submit evidence (representment): - Proof of delivery / service rendered - AVS/CVC verification results - 3DS authentication records - Customer communication logs - Signed agreements or terms β”‚ β–Ό 6. Card network reviews both sides and makes a ruling β”‚ β”Œβ”€β”€β”€β”΄β”€β”€β”€β” β–Ό β–Ό You win You lose (funds (funds stay with returned) cardholder; you absorb the loss + the fee) ``` ### Why Chargebacks Are So Damaging Chargebacks hurt far more than a simple refund: | Impact | Details | |---|---| | **Financial loss** | You lose the transaction amount + the chargeback fee + the cost of any goods/services already delivered | | **Chargeback ratio** | Card networks track your chargeback rate (chargebacks Γ· total transactions). If it exceeds **1%**, you face penalties, higher processing fees, or account termination | | **Operational cost** | Each dispute requires staff time to gather evidence, prepare documentation, and respond within tight deadlines | | **Reputation** | High chargeback rates can result in being placed on industry monitoring programs (Visa VDMP, Mastercard ECM), making it harder to get processing in the future | ### Common Chargeback Reasons | Category | Examples | |---|---| | **True fraud** | Stolen card used without cardholder's knowledge | | **Friendly fraud** | Cardholder made the purchase but claims they didn't (buyer's remorse, family member used the card) | | **Merchant error** | Wrong amount charged, duplicate charge, product not as described | | **Processing issue** | Charge appeared after cancellation, refund not processed in time | | **Unrecognized charge** | Cardholder doesn't recognize the merchant name on their statement | --- ## The Connection Between Anti-Fraud and Chargebacks Every fraudulent transaction that slips through your defenses is a chargeback waiting to happen. The real cardholder **will** dispute it. And in most fraud-related chargebacks, you lose β€” because you authorized a transaction with a stolen card. This is why anti-fraud isn't optional. It's directly tied to your bottom line: - **Prevent fraud** β†’ fewer chargebacks β†’ lower fees β†’ keep your merchant account - **Ignore fraud** β†’ chargeback ratio climbs β†’ penalties β†’ potential account termination ### The Pre-Authorization Advantage This is where [pre-authorization](apis/payment/pulling-funds/cards/authorizing/) (`capture: false`) becomes your most powerful tool. When you pre-authorize instead of directly capturing: 1. **Authorize the card** β€” funds are held, not settled 2. **Review the results** β€” check AVS, CVC, 3DS outcome, and run your fraud analysis 3. **Decide:** - βœ… Everything looks good β†’ [Capture](apis/payment/pulling-funds/cards/capture.md) the payment - ⚠️ Something is suspicious β†’ [Void](apis/payment/pulling-funds/cards/void.md) immediately β€” funds are released instantly, no chargeback risk If you had directly captured, you'd need to refund β€” and if the cardholder files a dispute before your refund processes, you get a chargeback anyway. --- ## Proactive Refunds: Your Best Defense When something goes wrong in the **post-transactional flow** β€” order can't be fulfilled, product is out of stock, service was not delivered as expected β€” **refund the customer immediately**. Don't wait for them to call their bank. **Why proactive refunds prevent chargebacks:** | Scenario | Bad outcome | Good outcome | |---|---|---| | Order can't be shipped | Customer waits, gets frustrated, disputes with bank β†’ chargeback | You refund immediately β†’ no dispute | | Product arrives damaged | Customer calls bank first β†’ chargeback + you lose the product | You process a refund on contact β†’ customer satisfied | | Subscription cancelled but still charged | Customer disputes β†’ chargeback + regulatory risk | You refund the erroneous charge β†’ clean resolution | | Duplicate charge | Customer sees two charges, panics, calls bank β†’ two chargebacks | You detect the duplicate, void or refund proactively β†’ zero friction | > **Rule of thumb:** A refund costs you the transaction amount. A chargeback costs you the transaction amount + the fee + staff time + damage to your chargeback ratio. **A voluntary refund is always cheaper than a chargeback.** ### Refund Timing Matters - If the payment is still in **AUTHORIZED** state (not yet captured): [**Void**](apis/payment/pulling-funds/cards/void.md) it. Instant. Clean. The cardholder sees the pending charge disappear. - If the payment is **CAPTURED**: [**Refund**](apis/payment/pulling-funds/cards/refund.md) it. The refund takes 3–10 business days to appear on the cardholder's statement, so communicate with the customer. - **Don't delay:** The longer a charge sits on a customer's statement without resolution, the more likely they are to file a dispute with their bank instead of contacting you. --- ## Anti-Fraud Best Practices ### 1. Use 3D Secure [3DS authentication](apis/payment/pulling-funds/cards/authorizing/handling-3d-secure.md) verifies the cardholder's identity with their bank. In markets where liability shift applies, a fully authenticated 3DS transaction shifts chargeback liability to the issuing bank β€” meaning you're protected even if fraud occurs. ### 2. Review AVS and CVC Results Every authorization response includes [AVS and CVC verification results](apis/payment/pulling-funds/cards/authorizing/handling-avs-cvc.md). Use them: - **CVC = FAILED** β†’ Strong fraud signal. Consider voiding even if the bank authorized. - **AVS = FAILED** β†’ Address mismatch. Combine with other signals before deciding. - **Both APPROVED** β†’ Lower risk, but not zero. Continue with other checks. ### 3. Implement Velocity Controls Set limits to detect abnormal patterns: - Maximum transactions per card per hour/day - Maximum total amount per card per day - Maximum failed attempts before temporary block - Unusual geographic patterns (card from one country, IP from another) ### 4. Secure Your API Keys - Never expose `clientId` or `secretId` in frontend code, repositories, or logs - Use the [Backend-for-Frontend (BFF) pattern](authentication-methods.md) β€” all API calls go through your server - Rotate credentials if you suspect compromise ### 5. Never Store Cardholder Data - Use [tokenization](apis/tokenizing-cards.md) for all card operations - Never store PAN, CVV, or expiration dates on your systems - Comply with PCI DSS requirements ### 6. Implement KYC Verify customer identities before processing high-value transactions: - Document verification (ID, passport, driver's license) - Address verification - Phone/email verification - Transaction scoring based on risk factors ### 7. Monitor Transactions Continuously - Set up [webhooks](apis/webhooks.md) to track payment status changes in real-time - Flag unusual patterns for manual review - Track your chargeback ratio daily β€” intervene before it approaches 1% ### 8. Educate Your Customers - Use a recognizable merchant name on card statements (reduces "unrecognized charge" disputes) - Send clear order confirmations and receipts - Make your refund policy easy to find and understand - Provide accessible customer support β€” customers who can reach you won't call their bank first ### 9. Keep Records Maintain detailed records for every transaction. If a chargeback does occur, you'll need: - Transaction timestamps and amounts - AVS/CVC/3DS verification results - IP address and device information - Proof of delivery or service rendered - Customer communication history - Signed terms and conditions --- ## Chargeback Response Checklist When you receive a chargeback notification: 1. **Don't ignore it** β€” You have a limited response window (typically 7–30 days) 2. **Gather evidence** β€” Pull transaction details, verification results, delivery proof, and communication logs 3. **Assess honestly** β€” If the customer is right (merchant error, unfulfilled order), accept the chargeback. Fighting a legitimate dispute wastes resources and damages your standing. 4. **Submit representment** β€” If you have strong evidence the transaction was legitimate, submit it through the chargeback response process 5. **Learn from it** β€” Every chargeback is a signal. Was it fraud you should have caught? A process gap? A customer service failure? Fix the root cause. --- ## Summary | Prevention Layer | Tools | |---|---| | **Before authorization** | KYC, velocity controls, device fingerprinting | | **During authorization** | 3DS, AVS, CVC, pre-authorization | | **After authorization** | Fraud review, void suspicious pre-auths | | **After capture** | Proactive refunds, clear communication, fast support | | **After dispute** | Evidence gathering, representment, root cause analysis | > **The best chargeback is the one that never happens.** Invest in prevention, use pre-authorization to give yourself a review window, and refund proactively when things go wrong. Your chargeback ratio β€” and your merchant account β€” depend on it. # APIs The Inyo Payment Gateway API provides endpoints for the complete payment lifecycle: | Endpoint | Method | Description | |---|---|---| | `/oauth/token` | POST | [Authenticate](../authentication-methods.md) and obtain an access token | | `/v2/payment` | POST | [Create a payment](payment/pulling-funds/cards/authorizing/) (pull, push, or pullpush) | | `/payments/{id}/capture` | POST | [Capture](payment/pulling-funds/cards/capture.md) a pre-authorized payment | | `/payments/{id}/void` | POST | [Void](payment/pulling-funds/cards/void.md) a pre-authorized payment | | `/payments/{id}/refund` | POST | [Refund](payment/pulling-funds/cards/refund.md) a captured payment | | `/payments/{id}` | GET | [Get payment details](payment/pulling-funds/listing-payments/by-external-id.md) | | `/payments` | GET | [List payments](payment/pulling-funds/listing-payments/get-a-list-of-payments.md) (paginated) | | `/foreign-exchange` | POST | [Get FX rate](payment/push-transaction.md#foreign-exchange) for cross-currency pushes | ## Additional Resources - [Tokenizing Cards](tokenizing-cards.md) β€” Client-side card tokenization with `inyo.js` - [Webhooks](webhooks.md) β€” Real-time transaction status notifications - [Domain Tables](domain-tables/) β€” Response codes and payment statuses - [Test Data](test-data/cards.md) β€” Sandbox test cards and simulation codes --- description: >- Securely tokenize card data client-side using the Inyo JavaScript library. Tokens replace sensitive card details for PCI-compliant payment processing. --- # Tokenizing Cards ## Overview PCI DSS requirements prohibit storing or transmitting raw card data unless you hold the appropriate certification. Inyo's tokenization solution handles this for you: the `inyo.js` library encrypts card data directly in the browser and returns a token that only your API keys can use. **How it works:** 1. Load `inyo.js` in your payment page 2. Add `data-field` attributes to your card input fields 3. Initialize `InyoTokenizer` with your public key and callbacks 4. Call `tokenizeCard()` when the user submits β€” the library reads the form, encrypts the data, and returns a token 5. Send the token to your backend to create a payment via `POST /v2/payment` ## Loading the Library Include the tokenizer script with an integrity hash to prevent tampering: ```html ``` | Environment | URL | |---|---| | **Sandbox** | `https://cdn.simpleps.com/sandbox/inyo.js` | | **Production** | `https://cdn.simpleps.com/production/inyo.js` | ## HTML Form Setup Add `data-field` attributes to your card input elements. The tokenizer binds to these automatically β€” no need to read input values yourself. ```html
``` **Required `data-field` values:** | `data-field` | Input | Notes | |---|---|---| | `cardholder` | Full name on card | As printed on the card | | `pan` | Card number | 13–19 digits | | `expirationDate` | Expiry | Format: `MM/YY` | | `securitycode` | CVV/CVC | 3 digits (Visa/MC/Discover) or 4 digits (Amex) | ## Initializing the Tokenizer Create an `InyoTokenizer` instance when the DOM is ready. The 3DS configuration depends on which integration method you choose β€” **URL Redirect** or **PostMessage**: ### Example β€” URL Redirect (traditional) The 3DS provider redirects the browser to your `successUrl` or `failUrl` after authentication. Best for server-rendered apps and full-page checkout flows. ```javascript document.addEventListener('DOMContentLoaded', () => { const tokenizer = new InyoTokenizer({ targetId: '#payment-form', publicKey: 'YOUR_PUBLIC_KEY', storeLaterUse: false, threeDSData: { enable: true, successUrl: 'https://yoursite.com/3ds/success', failUrl: 'https://yoursite.com/3ds/fail' }, successCallback: handleSuccess, errorCallback: handleError }); document.getElementById('pay-btn').addEventListener('click', () => { tokenizer.tokenizeCard(); }); }); ``` ### Example β€” PostMessage (SPA / embedded) The 3DS result is delivered via the browser's `postMessage` API to the parent window. Best for single-page apps and iframe/modal checkout experiences β€” no page navigation required. ```javascript document.addEventListener('DOMContentLoaded', () => { const tokenizer = new InyoTokenizer({ targetId: '#payment-form', publicKey: 'YOUR_PUBLIC_KEY', storeLaterUse: false, threeDSData: { enable: true, enablePostMessage: true }, successCallback: handleSuccess, errorCallback: handleError }); document.getElementById('pay-btn').addEventListener('click', () => { tokenizer.tokenizeCard(); }); }); ``` > When using `enablePostMessage: true`, you do **not** provide `successUrl` or `failUrl`. Instead, you listen for `message` events on the parent window after opening the `redirectAcsUrl` in an iframe. See [Handling 3D Secure](payment/pulling-funds/cards/authorizing/handling-3d-secure.md) for the complete implementation guide. ### Configuration Parameters | Parameter | Type | Required | Description | |---|---|---|---| | `targetId` | string | βœ… | CSS selector of the container holding the `data-field` inputs (e.g., `'#payment-form'`) | | `publicKey` | string | βœ… | Your merchant public key, provided by Inyo | | `successCallback` | function | βœ… | Called when tokenization succeeds | | `errorCallback` | function | βœ… | Called when tokenization fails | | `storeLaterUse` | boolean | ❌ | `false` (default) = one-time token; `true` = recurring/stored token | | `threeDSData` | object | ❌ | 3D Secure configuration (see below) | ### 3DS Configuration (`threeDSData`) | Field | Type | Required | Description | |---|---|---|---| | `enable` | boolean | βœ… | Whether to request 3DS authentication | | `successUrl` | string | ⚠️ | **Redirect mode only.** URL where the 3DS provider redirects on success | | `failUrl` | string | ⚠️ | **Redirect mode only.** URL where the 3DS provider redirects on failure | | `enablePostMessage` | boolean | ⚠️ | **PostMessage mode only.** Set `true` to receive 3DS results via `window.postMessage` instead of URL redirect | > **Choose one mode:** > - **Redirect:** Set `successUrl` + `failUrl`. Do not set `enablePostMessage`. > - **PostMessage:** Set `enablePostMessage: true`. Do not set `successUrl` / `failUrl`. > > Enabling 3DS does not guarantee a challenge will occur β€” the issuing bank decides based on its risk assessment. See [Handling 3D Secure](payment/pulling-funds/cards/authorizing/handling-3d-secure.md) for the full flow and code examples for both modes. ## Handling Callbacks ### Success Callback ```javascript function handleSuccess(response) { console.log('Tokenization response:', response); if (response.reasonCode === 'WAITING_TRANSACTION') { // Token created β€” extract it const token = response.additionalData.token; const lastFour = response.additionalData.lastFour; console.log(`Card ending in ${lastFour}, token: ${token}`); // Send the token to your backend server // Your backend will call POST /v2/payment with this token submitPaymentToBackend(token); } else { console.warn('Unexpected response step:', response.step); } } ``` **Success response fields:** | Field | Description | |---|---| | `reasonCode` | `"WAITING_TRANSACTION"` when token is ready | | `additionalData.token` | The card token UUID β€” use as `cardTokenId` in payment requests | | `additionalData.lastFour` | Last 4 digits of the card number (for display) | ### Error Callback ```javascript function handleError(response) { console.error('Tokenization error:', response); // Clear previous error states document.querySelectorAll('#cc-number, #cc-expiration, #cc-cvv') .forEach(el => el.classList.remove('is-invalid')); // Highlight the specific field that failed switch (response.code) { case 'INVALID_PAN': document.querySelector('#cc-number').classList.add('is-invalid'); break; case 'INVALID_EXPIRY_DATE': document.querySelector('#cc-expiration').classList.add('is-invalid'); break; case 'INVALID_CVV': document.querySelector('#cc-cvv').classList.add('is-invalid'); break; default: console.error('Unknown error code:', response.code); } } ``` **Error codes:** | Code | Description | |---|---| | `INVALID_PAN` | Card number is invalid (failed Luhn check or unsupported scheme) | | `INVALID_EXPIRY_DATE` | Expiration date is invalid or card is expired | | `INVALID_CVV` | Security code is invalid | ## One-Time vs. Recurring Tokens | Token Type | `storeLaterUse` | Usage | |---|---|---| | **One-time** | `false` | Single transaction only β€” cannot be reused | | **Recurring** | `true` | Can be stored and reused for future charges | **Rules:** - You **must not** store one-time tokens after use - You **may** store recurring tokens server-side for future transactions - You **must never** store raw card data (PAN, CVV, expiration) regardless of token type - When using a stored recurring token for a subsequent payment, pass the `previousPaymentId` (from the original authorization) alongside the `cardTokenId` ## Complete Examples ### Example A β€” URL Redirect (server-rendered checkout) ```html Inyo Payment β€” Redirect
``` ### Example B β€” PostMessage (SPA / embedded checkout) ```html Inyo Payment β€” PostMessage
``` ## Next Steps With a token in hand, proceed to [Authorizing a Card Payment](payment/pulling-funds/cards/authorizing/) to create the transaction via `POST /v2/payment`.
# Payment The Inyo Gateway supports three payment operation types: | Type | `paymentType` | Description | |---|---|---| | **Pull** | `"PULL"` | [Collect funds](pulling-funds/) from a customer (cards, ACH) | | **Push** | `"PUSH"` | [Send funds](push-transaction.md) to a recipient (cards, PIX, wallets, bank accounts) | | **Pull + Push** | `"PULLPUSH"` | [Collect and send](pull-and-push-in-one-step.md) in a single API call | All payment types use the same endpoint: ``` POST /v2/payment ``` The `paymentType` field in the request body determines the operation. --- description: >- Pull transactions collect funds from a customer's card or bank account. Supports credit/debit cards and ACH transfers. --- # Pulling Funds Pull (collect) transactions withdraw funds from a customer's payment method. The Inyo Gateway supports: - **Credit/Debit Cards** β€” Via tokenized card data (see [Tokenizing Cards](../../tokenizing-cards.md)) - **ACH Bank Transfers** β€” Via bank account and routing number All pull transactions use the same endpoint: ``` POST https://sandbox-gw.simpleps.com/v2/payment ``` ## Request Structure ### Root Object | Field | Type | Required | Description | |---|---|---|---| | `externalPaymentId` | string | βœ… | Your unique payment identifier (idempotency key) | | `ipAddress` | string | βœ… | Payer's IPv4 or IPv6 address | | `paymentType` | string | βœ… | `"PULL"` | | `capture` | boolean | βœ… | `true` = auto-capture; `false` = pre-auth only (cards only) | | `amount` | object | βœ… | Transaction amount | | `sender` | object | βœ… | Payer information, address, and payment method | ### `amount` Object | Field | Type | Required | Description | |---|---|---|---| | `total` | number | βœ… | Payment amount (must be β‰₯ 1) | | `currency` | string | βœ… | ISO 4217 currency code (e.g., `"USD"`) | ### `sender` Object | Field | Type | Required | Description | |---|---|---|---| | `firstName` | string | βœ… | Payer's first name | | `lastName` | string | βœ… | Payer's last name | | `address` | object | βœ… | Billing address | | `paymentMethod` | object | βœ… | Card or bank account details | ### `sender.address` Object | Field | Type | Required | Description | |---|---|---|---| | `countryCode` | string | βœ… | ISO Alpha-2 country code (e.g., `"US"`) | | `stateCode` | string | βœ… | State abbreviation (e.g., `"NY"`) | | `city` | string | βœ… | City name | | `line1` | string | βœ… | Street address line 1 | | `line2` | string | ❌ | Street address line 2 | | `zipCode` | string | βœ… | Postal/ZIP code | ### `sender.paymentMethod` β€” Card | Field | Type | Required | Description | |---|---|---|---| | `type` | string | βœ… | `"CARD"` | | `cardTokenId` | string | βœ… | Token UUID from the tokenizer | | `previousPaymentId` | string | ❌ | For recurring tokens: `paymentId` from the initial authorization | ### `sender.paymentMethod` β€” Bank Account (ACH) | Field | Type | Required | Description | |---|---|---|---| | `type` | string | βœ… | `"BANK_DEPOSIT"` | | `accountNumber` | string | βœ… | Bank account number (6–20 digits) | | `routingNumber` | string | βœ… | ABA routing number (9 digits) | | `accountType` | string | βœ… | `"CHECKING"`, `"SAVINGS"`, `"BUSINESS_CHECKING"`, or `"BUSINESS_SAVINGS"` | ## Card Payment Lifecycle ``` Tokenize β†’ Authorize β†’ (3DS Challenge?) β†’ Capture β†’ (Refund?) β†˜ Void ``` For card payments: 1. [**Authorize**](cards/authorizing/) β€” Create the payment (`"capture": false` for pre-auth) 2. [**Handle 3DS**](cards/authorizing/handling-3d-secure.md) β€” If `status` = `CHALLENGE`, redirect to `redirectAcsUrl` 3. [**Capture**](cards/capture.md) β€” Settle the authorized payment (within 7 days) 4. [**Void**](cards/void.md) β€” Cancel before capture 5. [**Refund**](cards/refund.md) β€” Return funds after capture (full or partial) ## ACH Payments ACH payments are always captured immediately (`"capture": true`). The pre-auth/capture lifecycle does not apply. See [ACH (Bank Account)](ach-bank-account.md) for a complete example. ## What's Next | Guide | Description | |---|---| | [Card Authorization](cards/authorizing/) | Full card payment flow with examples | | [ACH Payment](ach-bank-account.md) | Bank account pull with example payload | | [3D Secure](cards/authorizing/handling-3d-secure.md) | Challenge redirect handling | | [AVS / CVC](cards/authorizing/handling-avs-cvc.md) | Address and security code verification | | [Capture](cards/capture.md) | Settle a pre-authorized payment | | [Void](cards/void.md) | Cancel before settlement | | [Refund](cards/refund.md) | Return funds after capture | # Card Payments Card payments follow a lifecycle: **Tokenize β†’ Authorize β†’ (3DS?) β†’ Capture β†’ (Refund?)** | Step | Guide | |---|---| | 1. Tokenize | [Tokenizing Cards](../../../tokenizing-cards.md) | | 2. Authorize | [Authorizing a Card Payment](authorizing/) | | 3. Handle 3DS | [Handling 3D Secure](authorizing/handling-3d-secure.md) | | 4. Capture | [Capture](capture.md) | | 5. Void | [Void](void.md) (before capture) | | 6. Refund | [Refund](refund.md) (after capture) | For AVS and CVC verification details, see [Handling AVS / CVC](authorizing/handling-avs-cvc.md). --- description: >- Authorize a card payment by submitting a tokenized card to the /v2/payment endpoint. Supports pre-authorization and direct capture. --- # Authorizing a Card Payment After [tokenizing a card](../../../../tokenizing-cards.md), submit the token to create a payment authorization. You can choose between: - **Pre-authorization** (`"capture": false`) β€” Reserves funds without settling. You capture later when ready. - **Direct capture** (`"capture": true`) β€” Authorizes and captures in one step. Funds are settled immediately. --- ## Understanding Pre-Authorization vs. Direct Capture This is one of the most commonly misunderstood concepts in card payments. Getting it right determines how much control you have over your money flow β€” and how quickly you can reverse a transaction if something goes wrong. ### What is pre-authorization? When you set `"capture": false`, the gateway asks the cardholder's bank to **reserve** the funds on their card β€” but **no money moves yet**. The cardholder sees a "pending" charge on their statement, but settlement (the actual transfer of funds to your account) does not happen until you explicitly [capture](../capture.md) the payment. Think of it as placing a hold on the funds. The money is still in the cardholder's account, but they can't spend it elsewhere. ### Why use pre-authorization? Pre-authorization gives you a window to perform additional checks **before** committing to the financial settlement: - **AVS / CVC verification** β€” Review the address and security code results from the authorization response. If they indicate fraud risk, you can cancel immediately. - **Anti-fraud analysis** β€” Run the transaction through your fraud scoring system, manual review queues, or third-party fraud tools. - **Inventory / fulfillment checks** β€” Confirm the item is in stock, the service is available, or any business-specific validation passes. - **Compliance / KYC** β€” Verify the customer meets regulatory requirements before finalizing the transaction. - **Order adjustments** β€” The final amount may differ from the initial hold (e.g., partial shipments). You can capture a smaller amount than authorized. ### The key advantage: fast, clean reversal If you need to cancel a pre-authorized transaction, you perform a [**void**](../void.md). A void **instantly releases** the hold on the cardholder's funds β€” typically within minutes. The cardholder sees the pending charge disappear from their statement. No money was ever moved, so there's nothing to "return." Compare this to what happens after capture: once a transaction is captured, settlement has occurred. The money has left the cardholder's account and landed in yours. The **only** way to return funds at that point is a [**refund**](../refund.md), which is a separate financial transaction that can take **3–10 business days** to appear on the cardholder's statement. ### Side-by-side comparison | | Pre-authorization (`capture: false`) | Direct capture (`capture: true`) | |---|---|---| | **What happens** | Funds are held (reserved) on the card | Funds are held AND settled immediately | | **Money moves?** | ❌ No β€” just a hold | βœ… Yes β€” settlement begins | | **To cancel** | **Void** β€” instant, no money moved | **Refund** β€” separate transaction, 3–10 days | | **Cardholder sees** | "Pending" charge | Completed charge | | **Time limit** | Must capture within **7 days** or the hold expires automatically | N/A β€” already captured | | **Can adjust amount?** | βœ… Capture less than authorized | ❌ Amount is final | | **Best for** | E-commerce, travel, subscriptions, anything needing review | Simple point-of-sale, instant digital goods | ### When to use which **Use pre-authorization (`capture: false`) when:** - You need time to verify fraud signals, AVS/CVC results, or compliance - The final amount might change (partial shipments, tips, hotel incidentals) - You want the ability to cancel cleanly without a refund hitting the cardholder's statement - Your fulfillment has any delay between payment and delivery **Use direct capture (`capture: true`) when:** - The transaction is final and immediate (digital download, in-store purchase) - No review or adjustment is needed - You want the simplest integration with fewer API calls ### The lifecycle at a glance ``` capture: false capture: true ───────────── ───────────── POST /v2/payment POST /v2/payment β”‚ β”‚ β–Ό β–Ό AUTHORIZED CAPTURED (funds held) (funds settled) β”‚ β”‚ β”Œβ”€β”€β”€β”΄β”€β”€β”€β” β–Ό β”‚ β”‚ REFUNDED β–Ό β–Ό (3-10 days to CAPTURED VOIDED cardholder) (settle) (release hold, β”‚ instant) β–Ό REFUNDED (3-10 days) ``` > **Bottom line:** If there's any chance you might need to cancel the transaction after authorization, use pre-authorization. It's faster to reverse, cleaner for the cardholder, and gives you full control over the settlement timing. --- ## Endpoint ``` POST https://sandbox-gw.simpleps.com/v2/payment ``` **Headers:** | Header | Value | |---|---| | `Authorization` | `Bearer {accessToken}` | | `Content-Type` | `application/json` | ## Request Body ### Root Object | Field | Type | Required | Description | |---|---|---|---| | `externalPaymentId` | string | βœ… | Your unique identifier for this payment (idempotency key) | | `ipAddress` | string | βœ… | Payer's IPv4 or IPv6 address | | `paymentType` | string | βœ… | `"PULL"` | | `capture` | boolean | βœ… | `true` = auto-capture; `false` = pre-auth only | | `amount` | object | βœ… | Transaction amount | | `sender` | object | βœ… | Payer details | ### `amount` Object | Field | Type | Required | Description | |---|---|---|---| | `total` | number | βœ… | Amount to charge (must be β‰₯ 1) | | `currency` | string | βœ… | ISO 4217 currency code (e.g., `"USD"`) | ### `sender` Object | Field | Type | Required | Description | |---|---|---|---| | `firstName` | string | βœ… | Payer's first name | | `lastName` | string | βœ… | Payer's last name | | `address` | object | βœ… | Billing address (used for AVS verification) | | `paymentMethod` | object | βœ… | Card token details | ### `sender.address` Object | Field | Type | Required | Description | |---|---|---|---| | `countryCode` | string | βœ… | ISO Alpha-2 country code (e.g., `"US"`) | | `stateCode` | string | βœ… | State/province abbreviation (e.g., `"NY"`) | | `city` | string | βœ… | City name | | `line1` | string | βœ… | Street address line 1 | | `line2` | string | ❌ | Street address line 2 | | `zipCode` | string | βœ… | Postal/ZIP code | ### `sender.paymentMethod` Object (Card) | Field | Type | Required | Description | |---|---|---|---| | `type` | string | βœ… | `"CARD"` | | `cardTokenId` | string | βœ… | Token UUID from the tokenizer | | `previousPaymentId` | string | ❌ | Required for recurring tokens β€” the `paymentId` from the initial authorization | ## Example Request ```bash curl -X POST https://sandbox-gw.simpleps.com/v2/payment \ -H 'Authorization: Bearer eyJhbGciOiJSUzI1NiIs...' \ -H 'Content-Type: application/json' \ -d '{ "externalPaymentId": "order-12345", "ipAddress": "203.0.113.42", "paymentType": "PULL", "capture": false, "amount": { "total": 99.99, "currency": "USD" }, "sender": { "firstName": "John", "lastName": "Smith", "address": { "countryCode": "US", "stateCode": "NY", "city": "New York", "line1": "123 Main Street", "line2": "Apt 4B", "zipCode": "10001" }, "paymentMethod": { "type": "CARD", "cardTokenId": "ab5fc589-8b48-4531-94c0-68b0629c13fe" } } }' ``` ## Response ### Successful Authorization (200) ```json { "status": 200, "data": { "paymentId": "dce568c6-98ec-456c-bb33-4a6809c4fff8", "parentPaymentId": "dce568c6-98ec-456c-bb33-4a6809c4fff8", "externalPaymentId": "order-12345", "amount": 99.99, "created": "2025-03-31 03:49:05", "approved": true, "message": "Payment Approved", "automaticReversed": false, "status": "AUTHORIZED", "captured": false, "voided": false, "responseCode": "00", "issuerName": "BANK OF AMERICA", "issuerCountry": "UNITED STATES", "cvcResult": "APPROVED", "avsResult": "APPROVED", "avsCardholderNameResult": "N/A", "avsTelephoneResult": "N/A" } } ``` ### 3DS Challenge Required (200) When the issuing bank requires cardholder verification, the response returns `status: "CHALLENGE"` with a `redirectAcsUrl`: ```json { "status": 200, "data": { "paymentId": "dce568c6-98ec-456c-bb33-4a6809c4fff8", "externalPaymentId": "order-12345", "redirectAcsUrl": "https://sandbox-gw.simpleps.com/secure-code/start-challenge?token=dce568c6-...", "amount": 99.99, "approved": false, "message": "Payment awaiting 3DS challenge verification", "status": "CHALLENGE", "captured": false, "voided": false, "responseCode": "00", "cvcResult": "N/A", "avsResult": "N/A" } } ``` When you receive `CHALLENGE`, redirect the cardholder to `redirectAcsUrl`. See [Handling 3D Secure](handling-3d-secure.md) for the complete flow. ### Declined (400) ```json { "status": 400, "data": { "paymentId": "abc12345-...", "externalPaymentId": "order-12345", "amount": 99.99, "approved": false, "message": "Not sufficient funds", "status": "DECLINED", "responseCode": "PAY_084" } } ``` ## Response Fields | Field | Type | Description | |---|---|---| | `paymentId` | string | Inyo's unique payment identifier | | `parentPaymentId` | string | Parent payment ID (same as `paymentId` for initial authorizations) | | `externalPaymentId` | string | Your original external ID | | `redirectAcsUrl` | string | 3DS challenge URL (only present when `status` = `CHALLENGE`) | | `amount` | number | Transaction amount | | `created` | string | Timestamp (EST) | | `approved` | boolean | `true` if authorized successfully | | `message` | string | Human-readable status message | | `automaticReversed` | boolean | `true` if payment was auto-reversed by fraud rules | | `status` | string | `AUTHORIZED`, `CHALLENGE`, or `DECLINED` | | `captured` | boolean | `true` if auto-captured (`"capture": true` in request) | | `voided` | boolean | `true` if voided | | `responseCode` | string | Issuer/gateway response code (see [Response Codes](../../../../domain-tables/response-code.md)) | | `issuerName` | string | Name of the issuing bank | | `issuerCountry` | string | Country of the issuer | | `cvcResult` | string | `APPROVED`, `FAILED`, `NOT_SENT`, or `N/A` | | `avsResult` | string | `APPROVED`, `FAILED`, `NOT_SENT`, or `N/A` | ## Using a Recurring Token If the card was tokenized with `storeLaterUse: true`, you can reuse the token for future charges by including the `previousPaymentId`: ```json { "sender": { "paymentMethod": { "type": "CARD", "cardTokenId": "ab5fc589-8b48-4531-94c0-68b0629c13fe", "previousPaymentId": "dce568c6-98ec-456c-bb33-4a6809c4fff8" } } } ``` ## What's Next - **Authorized?** β†’ [Capture](../capture.md) the payment when ready to settle - **Need to cancel?** β†’ [Void](../void.md) the authorization before capture - **3DS Challenge?** β†’ [Handle 3D Secure](handling-3d-secure.md) redirect flow - **Check verification?** β†’ [AVS / CVC results](handling-avs-cvc.md) --- description: >- Understand AVS and CVC verification results to assess fraud risk on card payments. --- # Handling AVS / CVC Address Verification System (AVS) and Card Verification Code (CVC/CVV) checks are fraud prevention tools that compare customer-provided data against the card issuer's records. ## How It Works When you include the `sender.address` (billing address) in your payment request, the Inyo Gateway forwards it to the card network for verification. The results are returned in the payment response. > **Note:** AVS and CVC results do **not** directly control whether a payment is approved or declined. The issuing bank may authorize a payment even with mismatched AVS/CVC. These results are advisory β€” use them as signals in your own fraud decision logic. ## Response Fields Every payment response includes these verification fields: ```json { "cvcResult": "APPROVED", "avsResult": "APPROVED", "avsCardholderNameResult": "N/A", "avsTelephoneResult": "N/A" } ``` ## CVC Verification (`cvcResult`) CVC verifies the 3 or 4 digit security code on the card. | Status | Meaning | Action | |---|---|---| | `APPROVED` | CVV matches issuer records | βœ… Low fraud risk | | `FAILED` | CVV does not match | ⚠️ High fraud risk β€” consider voiding even if authorized | | `NOT_SENT` | CVV was not provided in the request | ℹ️ No verification performed | | `N/A` | Not applicable (e.g., during 3DS challenge) | ℹ️ Check after challenge completes | ## AVS Verification (`avsResult`) AVS compares the billing address (house number, street, and postal code) against the issuer's records. | Status | Meaning | Action | |---|---|---| | `APPROVED` | Address components match | βœ… Low fraud risk | | `FAILED` | Address components don't match | ⚠️ Elevated fraud risk | | `NOT_SENT` | No billing address was provided | ℹ️ No verification performed | | `N/A` | Not applicable (e.g., 3DS challenge pending) | ℹ️ Check after authorization | ## Configuration Options During merchant onboarding, you can configure AVS behavior at the Inyo level: 1. **Inyo-managed** β€” Inyo automatically evaluates AVS results and may decline or reverse transactions that fail verification, based on rules you define together. 2. **Self-managed** β€” Inyo returns the raw AVS/CVC results and your system decides how to handle them (e.g., void transactions with failed CVC). Contact the Inyo team to configure your preferred approach. ## Best Practices 1. **Always send billing address** β€” Even if AVS is advisory, providing address data improves authorization rates and gives you fraud signals. 2. **Combine signals** β€” Don't rely on a single check. Evaluate CVC + AVS + 3DS results together with transaction context (amount, velocity, device fingerprint). 3. **Void suspicious authorizations** β€” If a payment is authorized but CVC = `FAILED`, consider [voiding](../void.md) it before capture rather than accepting the risk. 4. **Log results** β€” Store AVS/CVC results for chargeback disputes. Demonstrating you verified the cardholder's identity strengthens your case. ## Testing AVS / CVC Use special values in sandbox to simulate different verification outcomes. See [Test Data β€” Cards](../../../../test-data/cards.md) for the full table. **CVC test values (Visa/MC/Discover):** | CVV Value | Result | |---|---| | `555` | Matched (APPROVED) | | `444` | Not matched (FAILED) | | _(empty)_ | Not supplied | **AVS test values (via zip code):** | Zip Code | Result | |---|---| | `AAAAA` | Full match (APPROVED) | | `JJJJJ` | No match (FAILED) | | _(empty)_ | Not supplied | --- description: >- Handle 3D Secure (3DS) authentication for card payments. Two integration options: URL redirect or JavaScript postMessage. --- # Handling 3D Secure 3D Secure (3DS) adds a layer of cardholder authentication to reduce fraud. When 3DS is triggered, the cardholder may need to verify their identity with their issuing bank before the payment is authorized. ## 3DS Modes | Mode | Cardholder Interaction | Description | |---|---|---| | **Data-only** | None | Transaction data is sent to the issuer for risk assessment; no cardholder action needed | | **Challenge** | Required | Cardholder verifies via OTP, biometrics, or bank app | > **Important:** In the US market, banks are not mandated to enforce step-up authentication. The issuing bank decides whether to issue a challenge, even if you request one during tokenization. 3DS in the AFT (Account Funding Transaction) market is for fraud control only β€” there is no liability shift. ## Enabling 3DS 3DS can be enabled in two ways: 1. **Automatic** (backend configuration) β€” All payments require 3DS. Configured by the Inyo team during onboarding. 2. **Manual** (per-transaction) β€” You control when to request 3DS by setting `threeDSData` during [card tokenization](../../../../tokenizing-cards.md). ## Integration Options When the payment API returns `status: "CHALLENGE"`, the cardholder must complete authentication. You have **two ways** to handle the challenge result: | Option | How it works | Best for | |---|---|---| | **URL Redirect** | Browser redirects to `redirectAcsUrl`; after authentication, the 3DS provider redirects back to your `successUrl` or `failUrl` | Full-page checkout flows, server-rendered apps | | **PostMessage** | Open `redirectAcsUrl` in an iframe; the 3DS provider sends a `postMessage` event to the parent window with the result | Single-page apps (SPAs), embedded/modal checkout experiences | --- ## Option 1: URL Redirect The traditional approach. The cardholder's browser navigates to the bank's 3DS page, and after authentication, is redirected back to your site. ### Tokenizer Configuration ```javascript const tokenizer = new InyoTokenizer({ targetId: '#payment-form', publicKey: 'YOUR_PUBLIC_KEY', threeDSData: { enable: true, successUrl: 'https://yoursite.com/3ds/success', failUrl: 'https://yoursite.com/3ds/fail' }, successCallback: handleSuccess, errorCallback: handleError }); ``` ### Flow ``` 1. Frontend tokenizes card β†’ receives token 2. Backend calls POST /v2/payment with token 3. API returns status: "CHALLENGE" + redirectAcsUrl 4. Frontend redirects browser to redirectAcsUrl 5. Cardholder completes bank authentication 6. Bank redirects to successUrl (approved) or failUrl (rejected) 7. Your server-side handler receives a POST with payment data 8. Backend confirms payment status via GET /payments/{id} ``` ### Step 4 β€” Redirect to Challenge ```javascript const response = await createPayment(paymentData); if (response.data.status === 'CHALLENGE' && response.data.redirectAcsUrl) { // Full-page redirect to bank's 3DS page window.location.href = response.data.redirectAcsUrl; } ``` ### Step 7a β€” Success URL Handler The 3DS provider sends a POST to your `successUrl` with payment data: ```json { "paymentId": "dce568c6-98ec-456c-bb33-4a6809c4fff8", "externalPaymentId": "order-12345", "amount": "99.99", "approved": "true" } ``` **Server-side handler example (Node.js / Express):** ```javascript // POST /3ds/success app.post('/3ds/success', async (req, res) => { const { paymentId, externalPaymentId } = req.body; // CRITICAL: Always verify via API β€” don't trust the redirect payload alone const payment = await fetch( `https://sandbox-gw.simpleps.com/payments/${externalPaymentId}`, { headers: { 'Authorization': `Bearer ${accessToken}` } } ).then(r => r.json()); if (payment.status === 'AUTHORIZED' || payment.status === 'CAPTURED') { // Payment confirmed β€” show success page res.redirect(`/order/${externalPaymentId}/confirmed`); } else { // Unexpected state β€” show error res.redirect(`/order/${externalPaymentId}/error`); } }); ``` ### Step 7b β€” Failure URL Handler The 3DS provider sends a POST to your `failUrl`: ```json { "paymentId": "dce568c6-98ec-456c-bb33-4a6809c4fff8", "externalId": "order-12345", "amount": 99.99, "approved": false, "status": "Rejected by ACS", "responseMsg": "Rejected by ACS", "responseCode": "99", "signatureVerification": "N", "acsChallengeRequired": true, "acsParStatus": "N" } ``` ```javascript // POST /3ds/fail app.post('/3ds/fail', (req, res) => { // Authentication failed β€” redirect user back to payment page res.redirect('/checkout?error=3ds_failed'); }); ``` > **Note:** When authentication fails, the payment was never authorized. Do not attempt to capture or void. --- ## Option 2: JavaScript PostMessage For single-page apps and embedded checkout experiences, you can open the 3DS challenge in an **iframe** and receive the result via the browser's `postMessage` API β€” no full-page redirect needed. ### Tokenizer Configuration Set `enablePostMessage: true` in `threeDSData`: ```javascript const tokenizer = new InyoTokenizer({ targetId: '#payment-form', publicKey: 'YOUR_PUBLIC_KEY', threeDSData: { enable: true, enablePostMessage: true }, successCallback: handleSuccess, errorCallback: handleError }); ``` > **Key difference:** When using `enablePostMessage: true`, you do **not** need to provide `successUrl` or `failUrl`. The result is delivered via `postMessage` instead of a redirect. ### Flow ``` 1. Frontend tokenizes card β†’ receives token 2. Backend calls POST /v2/payment with token 3. API returns status: "CHALLENGE" + redirectAcsUrl 4. Frontend opens redirectAcsUrl in an iframe (or modal) 5. Cardholder completes bank authentication inside the iframe 6. 3DS provider sends a postMessage to the parent window 7. Frontend listens for the message and handles the result 8. Backend confirms payment status via GET /payments/{id} ``` ### Step 4 β€” Open Challenge in Iframe ```html ``` ```javascript const response = await createPayment(paymentData); if (response.data.status === 'CHALLENGE' && response.data.redirectAcsUrl) { // Show the iframe container document.getElementById('threeds-container').style.display = 'block'; // Load the 3DS challenge page document.getElementById('threeds-iframe').src = response.data.redirectAcsUrl; } ``` ### Step 7 β€” Listen for PostMessage ```javascript window.addEventListener('message', async (event) => { // Validate the origin for security if (!event.origin.includes('simpleps.com')) return; const data = event.data; // Hide the iframe document.getElementById('threeds-container').style.display = 'none'; if (data.approved === true || data.approved === 'true') { // 3DS succeeded β€” verify payment status from your backend const paymentStatus = await verifyPaymentOnBackend(data.externalPaymentId); if (paymentStatus === 'AUTHORIZED' || paymentStatus === 'CAPTURED') { showSuccessMessage(); } else { showErrorMessage('Payment could not be confirmed.'); } } else { // 3DS failed β€” let user retry showErrorMessage('Card verification failed. Please try again or use a different card.'); } }); ``` ### PostMessage Payload β€” Success ```json { "paymentId": "dce568c6-98ec-456c-bb33-4a6809c4fff8", "externalPaymentId": "order-12345", "amount": 99.99, "approved": true } ``` ### PostMessage Payload β€” Failure ```json { "paymentId": "dce568c6-98ec-456c-bb33-4a6809c4fff8", "externalPaymentId": "order-12345", "amount": 99.99, "approved": false, "status": "Rejected by ACS", "responseCode": "99" } ``` ### Complete SPA Example ```javascript // Full 3DS handling for a single-page app class ThreeDSHandler { constructor() { this.iframe = document.getElementById('threeds-iframe'); this.container = document.getElementById('threeds-container'); this.pendingResolve = null; window.addEventListener('message', (event) => { if (!event.origin.includes('simpleps.com')) return; this.handleResult(event.data); }); } // Returns a promise that resolves when 3DS completes startChallenge(redirectAcsUrl) { return new Promise((resolve) => { this.pendingResolve = resolve; this.container.style.display = 'block'; this.iframe.src = redirectAcsUrl; }); } handleResult(data) { this.container.style.display = 'none'; this.iframe.src = ''; if (this.pendingResolve) { this.pendingResolve({ success: data.approved === true || data.approved === 'true', paymentId: data.paymentId, externalPaymentId: data.externalPaymentId }); this.pendingResolve = null; } } } // Usage in your checkout flow: const threeds = new ThreeDSHandler(); async function processPayment(paymentData) { const response = await createPayment(paymentData); if (response.data.status === 'CHALLENGE') { const result = await threeds.startChallenge(response.data.redirectAcsUrl); if (result.success) { // Verify on backend, then show confirmation const verified = await verifyPayment(result.externalPaymentId); showConfirmation(verified); } else { showRetryPrompt(); } } else if (response.data.status === 'AUTHORIZED') { showConfirmation(response.data); } else { showDeclineMessage(response.data.message); } } ``` --- ## Choosing Between Redirect and PostMessage | Consideration | URL Redirect | PostMessage | |---|---|---| | **User experience** | Full page navigation; user leaves your checkout | Seamless; challenge appears in iframe/modal | | **Implementation complexity** | Simpler β€” just set URLs | More code β€” manage iframe + event listener | | **Server requirements** | Need server-side POST handlers for success/fail URLs | No server handlers needed for 3DS result | | **SPA compatibility** | Requires workarounds (state loss on redirect) | Native fit for SPAs | | **Security** | Result delivered server-to-server (POST to your URL) | Result delivered client-side (validate origin!) | | **Mobile webview** | Works everywhere | Some webviews restrict iframe postMessage | **Recommendation:** - Use **URL Redirect** if you have a traditional server-rendered checkout or need maximum compatibility. - Use **PostMessage** if you're building an SPA or want an embedded checkout experience without page navigation. Regardless of which option you choose, **always verify the payment status via the API** (`GET /payments/{externalPaymentId}`) before fulfilling the order. --- ## Testing 3DS Use the 3DS-enabled test cards from the [Test Data](../../../../test-data/cards.md) page: | Card Number | Network | 3DS Type | |---|---|---| | `5413330033003303` | Mastercard | Challenge | | `5169527513596963` | Mastercard | Challenge | | `4983305199046950` | Visa | Challenge | | `5454545454545454` | Mastercard | Data-only | | `4975303994654672` | Visa | Data-only | | `6011926557021045` | Discover | Data-only | ## Implementation Checklist - [ ] Choose your integration method: URL Redirect or PostMessage - [ ] **If Redirect:** Configure `successUrl` and `failUrl` in tokenizer; implement server-side POST handlers - [ ] **If PostMessage:** Set `enablePostMessage: true` in tokenizer; implement `message` event listener with origin validation - [ ] Detect `status: "CHALLENGE"` in payment responses - [ ] Handle the 3DS result (success and failure) - [ ] **Always** verify payment status via GET API after 3DS completes - [ ] Test with 3DS test cards in sandbox - [ ] Handle edge cases: user closes iframe/tab, timeout, network errors --- description: >- Capture a pre-authorized payment to initiate settlement. Supports full and partial capture. --- # Capture Capturing a payment confirms that it's ready for financial settlement. This transfers the authorized funds from the cardholder's account to your merchant account. ## Key Rules - If not captured **within 7 days**, the authorization is **automatically voided** by the system - Settlement timing begins from the **moment of capture**, not authorization - A payment can only be captured **once** (but you can capture a partial amount) - If the payment was created with `"capture": true`, it was already captured β€” attempting again returns an error - If no amount is specified, the **full authorized amount** is captured ## Endpoint ``` POST https://sandbox-gw.simpleps.com/payments/{externalPaymentId}/capture ``` **Headers:** | Header | Value | |---|---| | `Authorization` | `Bearer {accessToken}` | | `Content-Type` | `application/json` | ## Request Body (Optional) Omit the body to capture the full authorized amount. Include it for a partial capture: ```json { "amount": { "total": 50.00, "currency": "USD" } } ``` | Field | Type | Required | Description | |---|---|---|---| | `amount.total` | number | ❌ | Amount to capture (must be ≀ authorized amount) | | `amount.currency` | string | ❌ | Currency code (must match the authorization) | ## Example β€” Full Capture ```bash curl -X POST https://sandbox-gw.simpleps.com/payments/order-12345/capture \ -H 'Authorization: Bearer eyJhbGciOiJSUzI1NiIs...' \ -H 'Content-Type: application/json' ``` ## Example β€” Partial Capture ```bash curl -X POST https://sandbox-gw.simpleps.com/payments/order-12345/capture \ -H 'Authorization: Bearer eyJhbGciOiJSUzI1NiIs...' \ -H 'Content-Type: application/json' \ -d '{ "amount": { "total": 50.00, "currency": "USD" } }' ``` ## Response (200) ```json { "paymentId": "bfb9eacb-7c72-4cc8-9cae-afd9164ec792", "parentPaymentId": "bfb9eacb-7c72-4cc8-9cae-afd9164ec792", "externalPaymentId": "order-12345", "amount": 100.10, "created": "2024-04-22 16:22:06", "approved": true, "message": "Payment Approved", "automaticReversed": false, "status": "CAPTURED", "captured": true, "voided": false, "authCode": "bfb9eacb-7c72-4cc8-9cae-afd9164ec792", "issuerName": "BANK OF AMERICA", "issuerCountry": "US", "cvcResult": "APPROVED", "avsResult": "NOT_SENT" } ``` For partial captures, the `status` will be `PARTIALLY_CAPTURED`. ## What's Next - **Captured?** β†’ You can now [refund](refund.md) the payment (full or partial) - **Changed your mind?** β†’ Only uncaptured payments can be [voided](void.md) --- description: >- Refund a captured payment, fully or partially. Multiple partial refunds are supported until the full amount is returned. --- # Refund Refunding reverses a previously captured payment and returns funds to the cardholder. The time for the refund to appear on the cardholder's statement varies by issuing bank. ## Key Rules - Only possible for **captured** payments (use [Void](void.md) for uncaptured authorizations) - You can refund the **full amount** or a **partial amount** - **Multiple partial refunds** are allowed until the full captured amount is refunded - Once fully refunded, further refund attempts return an error - If no amount is specified, the **full captured amount** is refunded ## Endpoint ``` POST https://sandbox-gw.simpleps.com/payments/{externalPaymentId}/refund ``` **Headers:** | Header | Value | |---|---| | `Authorization` | `Bearer {accessToken}` | | `Content-Type` | `application/json` | ## Request Body (Optional) Omit the body to refund the full captured amount. Include it for a partial refund: ```json { "amount": { "total": 25.00, "currency": "USD" } } ``` | Field | Type | Required | Description | |---|---|---|---| | `amount.total` | number | ❌ | Amount to refund (must be ≀ remaining captured amount) | | `amount.currency` | string | ❌ | Currency code (must match the original payment) | ## Example β€” Full Refund ```bash curl -X POST https://sandbox-gw.simpleps.com/payments/order-12345/refund \ -H 'Authorization: Bearer eyJhbGciOiJSUzI1NiIs...' \ -H 'Content-Type: application/json' ``` ## Example β€” Partial Refund ```bash curl -X POST https://sandbox-gw.simpleps.com/payments/order-12345/refund \ -H 'Authorization: Bearer eyJhbGciOiJSUzI1NiIs...' \ -H 'Content-Type: application/json' \ -d '{ "amount": { "total": 25.00, "currency": "USD" } }' ``` ## Response (200) ```json { "paymentId": "bfb9eacb-7c72-4cc8-9cae-afd9164ec792", "parentPaymentId": "bfb9eacb-7c72-4cc8-9cae-afd9164ec792", "externalPaymentId": "order-12345", "amount": 100.10, "created": "2024-04-22 16:22:06", "approved": true, "message": "Payment Approved", "automaticReversed": false, "status": "REFUNDED", "captured": true, "voided": false, "authCode": "bfb9eacb-7c72-4cc8-9cae-afd9164ec792", "issuerName": "BANK OF AMERICA", "issuerCountry": "US", "cvcResult": "APPROVED", "avsResult": "NOT_SENT" } ``` For partial refunds, the `status` will be `PARTIALLY_REFUNDED` until the full amount is returned. --- description: >- Void a pre-authorized payment to cancel it before settlement. The authorization is released back to the cardholder. --- # Void Voiding cancels an authorized payment **before capture**. The reserved funds are released back to the cardholder's account. ## Key Rules - Only possible if the payment has **not yet been captured** - Voids are **irreversible** β€” the payment cannot be re-captured after voiding - If the payment was already captured, use [Refund](refund.md) instead - The `externalPaymentId` identifies the payment to void ## Endpoint ``` POST https://sandbox-gw.simpleps.com/payments/{externalPaymentId}/void ``` **Headers:** | Header | Value | |---|---| | `Authorization` | `Bearer {accessToken}` | | `Content-Type` | `application/json` | ## Request Body None required. ## Example ```bash curl -X POST https://sandbox-gw.simpleps.com/payments/order-12345/void \ -H 'Authorization: Bearer eyJhbGciOiJSUzI1NiIs...' \ -H 'Content-Type: application/json' ``` ## Response (200) ```json { "paymentId": "bfb9eacb-7c72-4cc8-9cae-afd9164ec792", "parentPaymentId": "bfb9eacb-7c72-4cc8-9cae-afd9164ec792", "externalPaymentId": "order-12345", "amount": 100.10, "created": "2024-04-22 16:22:06", "approved": true, "message": "Payment Approved", "automaticReversed": false, "status": "VOIDED", "captured": false, "voided": true, "authCode": "bfb9eacb-7c72-4cc8-9cae-afd9164ec792", "issuerName": "BANK OF AMERICA", "issuerCountry": "US", "cvcResult": "APPROVED", "avsResult": "NOT_SENT" } ``` > **Note:** The original void doc showed `"status": "CAPTURED"` in the response β€” this was incorrect. A voided payment has `"status": "VOIDED"`. --- description: >- Pull funds from a US bank account using ACH. No tokenization required β€” provide bank details directly in the payment request. --- # ACH (Bank Account) ACH payments pull funds directly from a US bank account using the account number and routing number. Unlike card payments, ACH does not require tokenization β€” bank details are included directly in the API request. > **Note:** ACH payments are always captured immediately. Pre-authorization is not supported for ACH. ## Example Request ```bash curl -X POST https://sandbox-gw.simpleps.com/v2/payment \ -H 'Authorization: Bearer eyJhbGciOiJSUzI1NiIs...' \ -H 'Content-Type: application/json' \ -d '{ "externalPaymentId": "ach-order-001", "ipAddress": "203.0.113.42", "paymentType": "PULL", "capture": true, "amount": { "total": 19.99, "currency": "USD" }, "sender": { "firstName": "John", "lastName": "Smith", "address": { "countryCode": "US", "stateCode": "NY", "city": "New York", "line1": "123 Main Street", "line2": "Apt 25B", "zipCode": "10001" }, "paymentMethod": { "type": "BANK_DEPOSIT", "accountNumber": "123456789", "routingNumber": "021000021", "accountType": "CHECKING" } } }' ``` ## Payment Method Fields | Field | Type | Required | Description | |---|---|---|---| | `type` | string | βœ… | `"BANK_DEPOSIT"` | | `accountNumber` | string | βœ… | Bank account number (6–20 digits) | | `routingNumber` | string | βœ… | ABA routing number (9 digits) | | `accountType` | string | βœ… | Account type (see below) | ### Account Types | Value | Description | |---|---| | `CHECKING` | Personal checking account | | `SAVINGS` | Personal savings account | | `BUSINESS_CHECKING` | Business checking account | | `BUSINESS_SAVINGS` | Business savings account | ## Response ACH payments return the same response structure as card payments. Since `capture` is `true`, the status will be `CAPTURED` on success: ```json { "status": 200, "data": { "paymentId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "parentPaymentId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "externalPaymentId": "ach-order-001", "amount": 19.99, "created": "2025-03-31 10:15:30", "approved": true, "message": "Payment Approved", "status": "CAPTURED", "captured": true, "voided": false, "responseCode": "00" } } ``` ## Key Differences from Card Payments | Feature | Card | ACH | |---|---|---| | Tokenization required | βœ… Yes | ❌ No | | Pre-authorization | βœ… Supported | ❌ Not supported | | Capture/Void lifecycle | βœ… Full lifecycle | ❌ Auto-captured | | 3D Secure | βœ… Possible | ❌ Not applicable | | Refund | βœ… Supported | βœ… Supported | | Settlement speed | Same day – 2 days | 1–3 business days | # Listing Payments The Inyo Gateway provides endpoints to query transaction details and history: - [**Get a single payment**](by-external-id.md) β€” Retrieve full details and history for a specific transaction - [**List all payments**](get-a-list-of-payments.md) β€” Search and paginate through your transactions --- description: >- Retrieve full details, history, and fee breakdown for a specific payment using its external ID. --- # Get Payment by External ID Query the full details of a payment including its lifecycle history, fees, and card information. ## Endpoint ``` GET https://sandbox-gw.simpleps.com/payments/{externalPaymentId} ``` **Headers:** | Header | Value | |---|---| | `Authorization` | `Bearer {accessToken}` | ## Example Request ```bash curl -X GET https://sandbox-gw.simpleps.com/payments/order-12345 \ -H 'Authorization: Bearer eyJhbGciOiJSUzI1NiIs...' ``` ## Response (200) ```json { "parentPaymentId": "9a8dd6f3-105c-40a9-be13-ed95b2b2274b", "externalId": "order-12345", "requestedOn": "2025-01-21 17:15:14", "capturedOn": "2025-01-18 14:59:53", "refundedOn": "2025-01-21 17:15:14", "amount": 57.00, "capturedAmount": 57.00, "refundedAmount": 57.00, "amountRequested": 57.00, "currency": "USD", "status": "REFUNDED", "approved": true, "billing": { "stateCode": "FL", "city": "Orlando", "line1": "12516 Britwell Ct", "state": "FL", "zipCode": "32837" }, "ipAddress": "203.0.113.42", "customer": { "firstName": "MIKE", "lastName": "JOSEPH", "phoneNumber": "+1231232123", "email": "mike.joseph@example.com" }, "transactionFees": [ { "sourceId": "9a8dd6f3-...", "source": "PAYMENT", "eventId": "PRE_AUTH", "dtCreated": "2025-01-18 14:59:48", "amount": 0.57 }, { "sourceId": "0e444b74-...", "source": "PAYMENT", "eventId": "CAPTURE", "dtCreated": "2025-01-18 14:59:53", "amount": 0.00 }, { "sourceId": "9989afba-...", "source": "PAYMENT", "eventId": "REFUND", "dtCreated": "2025-01-21 17:15:14", "amount": 0.00 } ], "history": [ { "paymentId": "9a8dd6f3-...", "requestedOn": "2025-01-18 14:59:47", "code": "PAYMENT", "status": "AUTHORIZED", "description": "Payment Approved", "requestedAmount": 57.00 }, { "paymentId": "0e444b74-...", "requestedOn": "2025-01-18 14:59:53", "code": "CAPTURE", "status": "CAPTURED", "description": "Payment Approved", "requestedAmount": 57.00 }, { "paymentId": "9989afba-...", "requestedOn": "2025-01-21 17:15:14", "code": "REFUND", "status": "REFUNDED", "description": "Payment Approved", "requestedAmount": 57.00 } ], "card": { "lastFourDigits": "2929", "bin": "479213", "schemeId": "VISA", "issuer": "TD BANK, NATIONAL ASSOCIATION", "country": "UNITED STATES", "countryCode": "US", "cardType": "DEBIT", "cardCategory": "CLASSIC", "currencyCode": "USD" } } ``` ## Response Fields ### Root | Field | Type | Description | |---|---|---| | `parentPaymentId` | string | Root payment ID | | `externalId` | string | Your external payment ID | | `requestedOn` | string | Timestamp of the most recent operation | | `capturedOn` | string | Capture timestamp (null if not captured) | | `refundedOn` | string | Refund timestamp (null if not refunded) | | `amount` | number | Original authorized amount | | `capturedAmount` | number | Amount that was captured | | `refundedAmount` | number | Amount that was refunded | | `currency` | string | ISO 4217 currency code | | `status` | string | Current payment status | | `approved` | boolean | Whether the payment was approved | ### `transactionFees[]` | Field | Description | |---|---| | `eventId` | Fee event: `PRE_AUTH`, `CAPTURE`, `REFUND` | | `amount` | Fee amount for this event | | `dtCreated` | When the fee was incurred | ### `history[]` | Field | Description | |---|---| | `paymentId` | ID of the specific operation | | `code` | Operation type: `PAYMENT`, `CAPTURE`, `REFUND` | | `status` | Status after this operation | | `requestedAmount` | Amount for this operation | ### `card` | Field | Description | |---|---| | `lastFourDigits` | Last 4 digits of the card | | `bin` | Bank Identification Number (first 6 digits) | | `schemeId` | Card network: `VISA`, `MASTERCARD`, etc. | | `issuer` | Issuing bank name | | `cardType` | `DEBIT` or `CREDIT` | | `cardCategory` | Card tier (e.g., `CLASSIC`, `GOLD`, `PLATINUM`) | --- description: >- Retrieve a paginated list of all payments. Useful for reconciliation, reporting, and transaction management. --- # List Payments Retrieve all transactions in a paginated format. ## Endpoint ``` GET https://sandbox-gw.simpleps.com/payments?resultsPerPage={size}&page={number} ``` **Headers:** | Header | Value | |---|---| | `Authorization` | `Bearer {accessToken}` | ### Query Parameters | Parameter | Type | Default | Description | |---|---|---|---| | `resultsPerPage` | number | 10 | Number of results per page | | `page` | number | 0 | Page number (zero-indexed) | ## Example Request ```bash curl -X GET 'https://sandbox-gw.simpleps.com/payments?resultsPerPage=10&page=0' \ -H 'Authorization: Bearer eyJhbGciOiJSUzI1NiIs...' ``` ## Response (200) Returns an array of payment objects. Each object has the same structure as the [single payment response](by-external-id.md), including `history`, `transactionFees`, `card`, `billing`, and `customer` fields. ```json [ { "parentPaymentId": "9a8dd6f3-...", "externalId": "order-12345", "requestedOn": "2025-01-21 17:15:14", "amount": 57.00, "currency": "USD", "status": "REFUNDED", "approved": true, "history": [ ... ], "transactionFees": [ ... ], "card": { ... } }, { "parentPaymentId": "b2c3d4e5-...", "externalId": "order-12346", "requestedOn": "2025-01-22 09:30:00", "amount": 150.00, "currency": "USD", "status": "CAPTURED", "approved": true, "history": [ ... ], "card": { ... } } ] ``` ## Pagination Use `page` and `resultsPerPage` to iterate through results: ```bash # First page GET /payments?resultsPerPage=25&page=0 # Second page GET /payments?resultsPerPage=25&page=1 ``` Increment `page` until the response returns fewer results than `resultsPerPage`, indicating you've reached the last page. --- description: >- Push transactions send funds from your account to a recipient's card, bank account, PIX key, or wallet β€” domestically or internationally. --- # Push Transaction Push (payout) transactions transfer funds to a recipient. The Inyo Gateway supports multiple destination methods: - **Cards** β€” Visa Direct / Mastercard Send (domestic and cross-border) - **ACH** β€” US domestic bank transfers - **PIX** β€” Brazilian instant payments - **Wallets** β€” Digital wallet payouts - **Bank Accounts** β€” Cross-border account payouts ## Endpoint ``` POST https://sandbox-gw.simpleps.com/v2/payment ``` **Headers:** | Header | Value | |---|---| | `Authorization` | `Bearer {accessToken}` | | `Content-Type` | `application/json` | ## Request Structure ### Root Object | Field | Type | Required | Description | |---|---|---|---| | `externalPaymentId` | string | βœ… | Your unique payment identifier | | `ipAddress` | string | βœ… | Originator's IPv4 or IPv6 address | | `paymentType` | string | βœ… | `"PUSH"` | | `amount` | object | βœ… | Source amount (what you're sending) | | `recipientAmount` | object | βœ… | Destination amount (what recipient receives) | | `exchangeRate` | number | βœ… | FX rate applied between currencies | | `sender` | object | βœ… | Sender details | | `recipient` | object | βœ… | Recipient details and destination | ### `amount` Object (Source) | Field | Type | Required | Description | |---|---|---|---| | `total` | number | βœ… | Amount to send (must be β‰₯ 1) | | `currency` | string | βœ… | Source currency (e.g., `"USD"`) | ### `recipientAmount` Object (Destination) | Field | Type | Required | Description | |---|---|---|---| | `total` | number | βœ… | Amount recipient receives | | `currency` | string | βœ… | Destination currency (ISO 4217, e.g., `"BRL"`) | ### `sender` Object | Field | Type | Required | Description | |---|---|---|---| | `customer` | object | βœ… | Sender's personal information | | `customerAddress` | object | βœ… | Sender's address | #### `sender.customer` | Field | Type | Required | Description | |---|---|---|---| | `firstName` | string | βœ… | Sender's first name | | `lastName` | string | βœ… | Sender's last name | | `phoneNumber` | string | βœ… | Phone (7–15 digits) | | `documentNumber` | string | βœ… | ID document number (5–20 digits) | | `documentType` | string | βœ… | `NATIONAL_ID`, `PASSPORT`, or `DRIVER_LICENSE` | | `email` | string | βœ… | Email address | | `countryCodeAlpha3` | string | βœ… | ISO Alpha-3 country code (e.g., `"USA"`) | #### `sender.customerAddress` | Field | Type | Required | Description | |---|---|---|---| | `stateCode` | string | βœ… | State abbreviation (e.g., `"CA"`) | | `city` | string | βœ… | City name | | `line1` | string | βœ… | Street address line 1 | | `line2` | string | ❌ | Street address line 2 | | `zipCode` | string | βœ… | Postal/ZIP code | | `countryCode` | string | ❌ | ISO Alpha-2 country code | ### `recipient` Object | Field | Type | Required | Description | |---|---|---|---| | `customer` | object | βœ… | Recipient's personal information | | `customerAddress` | object | βœ… | Recipient's address | | `destination` | object | βœ… | Payout method and details | #### `recipient.customer` Same fields as `sender.customer`. #### `recipient.customerAddress` Same fields as `sender.customerAddress`. ### `recipient.destination` β€” PIX | Field | Type | Required | Description | |---|---|---|---| | `type` | string | βœ… | `"PIX"` | | `pix.keyType` | string | βœ… | `"EMAIL"`, `"PHONE"`, `"DOCUMENT"` (CPF/CNPJ), or `"EVP"` (random key) | | `pix.key` | string | βœ… | The PIX key value | ### `recipient.destination` β€” Wallet | Field | Type | Required | Description | |---|---|---|---| | `type` | string | βœ… | `"WALLET"` | | `wallet.walletId` | string | βœ… | Wallet identifier (email or ID) | ## Example β€” Push to PIX ```bash curl -X POST https://sandbox-gw.simpleps.com/v2/payment \ -H 'Authorization: Bearer eyJhbGciOiJSUzI1NiIs...' \ -H 'Content-Type: application/json' \ -d '{ "externalPaymentId": "push-001", "ipAddress": "203.0.113.42", "paymentType": "PUSH", "amount": { "total": 55.00, "currency": "USD" }, "recipientAmount": { "total": 273.63, "currency": "BRL" }, "exchangeRate": 4.975, "sender": { "customer": { "firstName": "John", "lastName": "Smith", "phoneNumber": "5551234567", "documentNumber": "050482156", "documentType": "NATIONAL_ID", "email": "john.smith@example.com", "countryCodeAlpha3": "USA" }, "customerAddress": { "stateCode": "CA", "city": "Los Angeles", "line1": "4429 Candlewood St", "zipCode": "90712", "countryCode": "US" } }, "recipient": { "customer": { "firstName": "Carlos", "lastName": "Silva", "phoneNumber": "1122334455", "documentNumber": "12345678900", "documentType": "PASSPORT", "email": "carlos.silva@example.com", "countryCodeAlpha3": "BRA" }, "customerAddress": { "stateCode": "RJ", "city": "Rio de Janeiro", "line1": "Rua das Laranjeiras 321", "state": "Rio de Janeiro", "zipCode": "22240-005" }, "destination": { "type": "PIX", "pix": { "keyType": "DOCUMENT", "key": "01034861788" } } } }' ``` ## Example β€” Push to Wallet ```bash curl -X POST https://sandbox-gw.simpleps.com/v2/payment \ -H 'Authorization: Bearer eyJhbGciOiJSUzI1NiIs...' \ -H 'Content-Type: application/json' \ -d '{ "externalPaymentId": "push-002", "ipAddress": "203.0.113.42", "paymentType": "PUSH", "amount": { "total": 100.00, "currency": "USD" }, "recipientAmount": { "total": 497.50, "currency": "BRL" }, "exchangeRate": 4.975, "sender": { "customer": { "firstName": "Jane", "lastName": "Doe", "phoneNumber": "5559876543", "documentNumber": "123456789", "documentType": "NATIONAL_ID", "email": "jane.doe@example.com", "countryCodeAlpha3": "USA" }, "customerAddress": { "stateCode": "NY", "city": "New York", "line1": "456 Park Avenue", "zipCode": "10022", "countryCode": "US" } }, "recipient": { "customer": { "firstName": "Ana", "lastName": "Souza", "phoneNumber": "21987654321", "documentNumber": "10987654321", "documentType": "PASSPORT", "email": "ana.souza@example.com", "countryCodeAlpha3": "BRA" }, "customerAddress": { "stateCode": "RJ", "city": "Rio de Janeiro", "line1": "Rua da AlfΓ’ndega 45", "state": "Rio de Janeiro", "zipCode": "20010-030" }, "destination": { "type": "WALLET", "wallet": { "walletId": "ana.souza@wallet.com" } } } }' ``` ## Foreign Exchange For cross-currency push payments, you can fetch real-time FX rates before submitting: ```bash curl -X POST https://sandbox-gw.simpleps.com/foreign-exchange \ -H 'Authorization: Bearer eyJhbGciOiJSUzI1NiIs...' \ -H 'Content-Type: application/json' \ -d '{ "sourceCurrencyCode": "USD", "destinationCurrencyCode": "BRL" }' ``` **Response:** ```json { "fxId": "a1b2c3d4-...", "conversionRate": 4.975, "quoteIdExpiryDateTime": "2025-03-31T15:30:00Z" } ``` Use the `conversionRate` as the `exchangeRate` in your push payment, and optionally pass `fxId` to lock in the quoted rate. > **Note:** FX quotes have a limited validity period. Check `quoteIdExpiryDateTime` and refresh if expired. --- description: >- Combine a pull (collect) and push (payout) in a single API call for streamlined fund collection and disbursement. --- # Pull and Push in One Step The `PULLPUSH` payment type combines fund collection and disbursement into a single API call. Funds are pulled from the sender's card or bank account and simultaneously pushed to the recipient's destination. This reduces latency and simplifies workflows where funds need to move between two parties in a single transaction. ## Endpoint ``` POST https://sandbox-gw.simpleps.com/v2/payment ``` **Headers:** | Header | Value | |---|---| | `Authorization` | `Bearer {accessToken}` | | `Content-Type` | `application/json` | ## Request Structure ### Root Object | Field | Type | Required | Description | |---|---|---|---| | `externalPaymentId` | string | βœ… | Your unique payment identifier | | `ipAddress` | string | βœ… | Originator's IPv4 or IPv6 address | | `paymentType` | string | βœ… | `"PULLPUSH"` | | `amount` | object | βœ… | Pull amount (sender side, in USD) | | `recipientAmount` | object | βœ… | Push amount (recipient side, any currency) | | `exchangeRate` | number | βœ… | FX rate between source and destination currencies | | `sender` | object | βœ… | Sender details and payment source | | `recipient` | object | βœ… | Recipient details and payout destination | ### Sender The sender structure is identical to a [Pull payment](pulling-funds/) β€” includes `customer`, `customerAddress`, and `source`: #### `sender.source` β€” Card | Field | Type | Required | Description | |---|---|---|---| | `type` | string | βœ… | `"CARD"` | | `card.token` | string | βœ… | Card token UUID from tokenizer | #### `sender.source` β€” Bank Account | Field | Type | Required | Description | |---|---|---|---| | `type` | string | βœ… | `"BANK_ACCOUNT"` | | `accountType` | string | βœ… | `"savings"`, `"checking"`, etc. | | `accountNumber` | string | βœ… | Bank account number | | `routingNumber` | string | βœ… | ABA routing number | | `accountHolder` | object | βœ… | `{ type, firstName, lastName }` | ### Recipient The recipient structure is identical to a [Push payment](push-transaction.md) β€” includes `customer`, `customerAddress`, and `destination`. ## Example β€” PULLPUSH (Card β†’ Wallet) ```bash curl -X POST https://sandbox-gw.simpleps.com/v2/payment \ -H 'Authorization: Bearer eyJhbGciOiJSUzI1NiIs...' \ -H 'Content-Type: application/json' \ -d '{ "externalPaymentId": "pullpush-001", "ipAddress": "203.0.113.42", "paymentType": "PULLPUSH", "amount": { "total": 100.00, "currency": "USD" }, "recipientAmount": { "total": 497.50, "currency": "BRL" }, "exchangeRate": 4.975, "sender": { "customer": { "firstName": "Lucas", "lastName": "Silva", "phoneNumber": "11999999999", "documentNumber": "12345678900", "documentType": "NATIONAL_ID", "email": "lucas.silva@example.com", "countryCodeAlpha3": "USA" }, "customerAddress": { "stateCode": "CA", "city": "San Francisco", "line1": "123 Mission St", "line2": "Suite 900", "state": "California", "zipCode": "94105" }, "source": { "type": "CARD", "card": { "token": "ab5fc589-8b48-4531-94c0-68b0629c13fe" } } }, "recipient": { "customer": { "firstName": "Ana", "lastName": "Souza", "phoneNumber": "21987654321", "documentNumber": "10987654321", "documentType": "PASSPORT", "email": "ana.souza@example.com", "countryCodeAlpha3": "BRA" }, "customerAddress": { "stateCode": "RJ", "city": "Rio de Janeiro", "line1": "Rua da AlfΓ’ndega 45", "state": "Rio de Janeiro", "zipCode": "20010-030" }, "destination": { "type": "WALLET", "wallet": { "walletId": "ana.souza@wallet.com" } } } }' ``` ## Example β€” PULLPUSH (Bank Account β†’ PIX) ```bash curl -X POST https://sandbox-gw.simpleps.com/v2/payment \ -H 'Authorization: Bearer eyJhbGciOiJSUzI1NiIs...' \ -H 'Content-Type: application/json' \ -d '{ "externalPaymentId": "pullpush-002", "ipAddress": "203.0.113.42", "paymentType": "PULLPUSH", "amount": { "total": 200.00, "currency": "USD" }, "recipientAmount": { "total": 995.00, "currency": "BRL" }, "exchangeRate": 4.975, "sender": { "customer": { "firstName": "Maria", "lastName": "Johnson", "phoneNumber": "5551234567", "documentNumber": "987654321", "documentType": "DRIVER_LICENSE", "email": "maria.johnson@example.com", "countryCodeAlpha3": "USA" }, "customerAddress": { "stateCode": "TX", "city": "Austin", "line1": "789 Congress Ave", "state": "Texas", "zipCode": "78701" }, "source": { "type": "BANK_ACCOUNT", "accountType": "checking", "accountNumber": "123456789", "routingNumber": "021000021", "accountHolder": { "type": "personal", "firstName": "Maria", "lastName": "Johnson" } } }, "recipient": { "customer": { "firstName": "Pedro", "lastName": "Santos", "phoneNumber": "11987654321", "documentNumber": "98765432100", "documentType": "NATIONAL_ID", "email": "pedro.santos@example.com", "countryCodeAlpha3": "BRA" }, "customerAddress": { "stateCode": "SP", "city": "SΓ£o Paulo", "line1": "Av Paulista 1000", "state": "SΓ£o Paulo", "zipCode": "01311-100" }, "destination": { "type": "PIX", "pix": { "keyType": "EMAIL", "key": "pedro.santos@example.com" } } } }' ``` ## Key Differences from Separate Pull + Push | Aspect | Separate calls | PULLPUSH | |---|---|---| | API calls | 2 (one pull, one push) | 1 | | Atomicity | Independent β€” push can fail after pull succeeds | Single transaction | | FX rate | Must manage independently | Specified in same request | | Use case | Complex flows with intermediate logic | Straight-through remittance | --- description: >- Receive real-time notifications when payment statuses change. Configure webhooks to automate your payment workflow. --- # Webhooks Webhooks deliver real-time notifications to your server when a payment reaches a specific status. Instead of polling the API for updates, your system receives a POST request with the payment details. ## Supported Events | Event | Trigger | Typical Action | |---|---|---| | `AUTHORIZED` | Payment successfully authorized (funds reserved) | Confirm payment method is valid | | `CAPTURED` | Payment captured and funds settled | Fulfill order, release goods | | `CHALLENGE` | 3DS challenge initiated; awaiting cardholder verification | Wait for follow-up (`AUTHORIZED` or `DECLINED`) | | `DECLINED` | Payment rejected by issuer or fraud rules | Prompt user for another method | | `VOIDED` | Authorization cancelled before capture | Update order as cancelled | | `REFUNDED` | Funds returned to cardholder (full or partial) | Confirm refund in your system | ## Webhook Configuration Webhook endpoints are configured during merchant onboarding. Contact the Inyo team to: 1. Provide your webhook URL(s) 2. Select which events you want to receive 3. Configure authentication (Basic auth or Bearer token) > **Webhook registration API**: Webhooks can also be managed programmatically via the notification endpoints. See the [OpenAPI spec](https://sandbox-gw.simpleps.com/q/swagger-ui/#/) for `POST`, `GET`, and `DELETE /notification` endpoints. ## Webhook Payload All webhook events deliver the same JSON structure β€” the full payment object at the time of the event: ```json { "paymentId": "469670e2-31e3-4700-b9f2-abee99418dc4", "parentPaymentId": "469670e2-31e3-4700-b9f2-abee99418dc4", "externalPaymentId": "order-12345", "redirectAcsUrl": "", "amount": 95.00, "created": "2025-01-21 16:31:02", "approved": true, "message": "Payment Approved", "acquirerMessage": "IN_PROCESS_AUTHORISED", "automaticReversed": false, "status": "AUTHORIZED", "captured": false, "voided": false, "authCode": "469670e2-31e3-4700-b9f2-abee99418dc4", "issuerName": "BANK OF AMERICA", "issuerCountry": "UNITED STATES", "cvcResult": "NOT_SENT", "avsResult": "NOT_SENT", "aavAddressResult": "NOT_CHECKED", "aavPostcodeResult": "NOT_CHECKED", "aavCardholderNameResult": "N/A", "aavTelephoneResult": "NOT_CHECKED" } ``` ## Payload Fields See [Authorizing a Card Payment](payment/pulling-funds/cards/authorizing/) for a full description of all response fields. The webhook payload is identical to the payment response object. ## Handling Webhooks ### Best Practices 1. **Return 200 quickly** β€” Acknowledge receipt before doing heavy processing. Queue the event for async handling. 2. **Verify the source** β€” Validate that webhook requests come from Inyo's IP range or verify the authentication credentials. 3. **Handle duplicates** β€” Webhooks may be delivered more than once. Use `paymentId` + `status` as an idempotency key. 4. **Check `status`, not just `approved`** β€” The `status` field (`AUTHORIZED`, `CAPTURED`, `DECLINED`, etc.) is the authoritative state of the payment. 5. **Don't rely solely on webhooks** β€” Always confirm the payment status via the [GET payment endpoint](payment/pulling-funds/listing-payments/by-external-id.md) before fulfilling orders. Webhooks are notifications, not the source of truth. ### Example Handler (Node.js) ```javascript app.post('/webhooks/inyo', (req, res) => { // Acknowledge immediately res.status(200).send('OK'); const payment = req.body; const { externalPaymentId, status, paymentId } = payment; switch (status) { case 'AUTHORIZED': // Payment authorized β€” ready to capture markOrderAsAuthorized(externalPaymentId, paymentId); break; case 'CAPTURED': // Funds settled β€” fulfill the order fulfillOrder(externalPaymentId); break; case 'DECLINED': // Payment failed β€” notify customer notifyDecline(externalPaymentId, payment.message); break; case 'VOIDED': // Authorization cancelled cancelOrder(externalPaymentId); break; case 'REFUNDED': // Funds returned processRefund(externalPaymentId, payment.amount); break; } }); ``` ## Retry Policy If your endpoint returns a non-2xx response or times out, Inyo will retry delivery with exponential backoff. Ensure your handler is idempotent to handle retries safely. # Domain Tables Reference tables for payment processing: - [**Payment Status**](payment-status.md) β€” All possible payment lifecycle states - [**Response Codes**](response-code.md) β€” Gateway and issuer response codes with descriptions --- description: >- The Inyo Gateway uses a comprehensive set of response codes to indicate the status and results of transaction processing --- # Response Code Each response code is associated with a specific context and HTTP status code, providing clarity and actionable insights.. ## Customer-Facing Messages β€” Security Guidelines > **⚠️ Important:** Internal response codes must **never** be exposed directly to end users. Detailed error codes reveal information about your payment infrastructure, fraud rules, and issuer behavior that can be exploited by bad actors to refine fraudulent transactions. Instead, map internal codes to **generic, user-friendly messages**. The table below provides a recommended mapping: ### Recommended Customer-Facing Messages #### "Check if the card information provided is correct." Use for tokenization errors, invalid card data, CVV/expiry mismatches, and address verification failures. | Codes | | --------------------------------------------------------------------------------------------------------------------------------- | | `CAD_001` `CAD_002` `CAD_003` `CAD_004` `CAD_005` `CARD_010` `CA_006` `CA_007` | | `PAY_035` `PAY_041` `PAY_046` `PAY_045` `PAY_029` `PAY_087` | | `PAY_050` `PAY_049` `PAY_048` `PAY_047` `PAY_052` `PAY_051` `PAY_054` `PAY_053` `PAY_056` `PAY_055` `PAY_058` `PAY_057` `PAY_059` | #### "Additional verification is required. Please try again and complete verification." Use for PIN-related errors, 3DS failures, and additional authentication requests. | Codes | | ----------------------------------------------------------- | | `PAY_088` `PAY_097` `PAY_099` `PAY_107` `PAY_113` `PAY_114` | | `PAY_122` `PAY_123` `PAY_124` `PAY_125` `PAY_146` | #### "Your bank declined this payment. Please contact your bank or try another payment method." Use for issuer declines, card restrictions, expired cards, and network-level rejections. This is the catch-all for most issuer-driven failures. | Codes | | ------------------------------------------------------------------------------- | | `PAY_067` `PAY_063` `PAY_064` `PAY_680` `PAY_700` `PAY_699` `PAY_042` | | `PAY_090` `PAY_089` `PAY_072` `PAY_073` `PAY_074` `PAY_075` | | `PAY_079` `PAY_078` `PAY_080` `PAY_081` `PAY_082` `PAY_083` `PAY_085` `PAY_086` | | `PAY_091` `PAY_092` `PAY_093` `PAY_094` `PAY_096` | | `PAY_100` `PAY_101` `PAY_102` `PAY_103` `PAY_104` `PAY_105` `PAY_106` | | `PAY_108` `PAY_109` `PAY_110` `PAY_112` | | `PAY_116` `PAY_117` `PAY_118` `PAY_119` `PAY_120` `PAY_121` | | `PAY_126` `PAY_127` `PAY_128` `PAY_130` `PAY_131` `PAY_132` `PAY_133` | #### "This payment couldn't be processed. Try another payment method or contact support." Use for fraud/risk blocks, blacklist hits, and gateway-level rejections. Keep the message intentionally vague β€” do not hint at fraud detection. | Codes | | ----------------------------------------------------------- | | `PAY_030` `PAY_038` `PAY_039` `PAY_040` | | `PAY_139` `PAY_140` `PAY_141` `PAY_145` `PAY_066` `PAY_508` | #### "Insufficient funds. Please use another payment method or add funds and try again." Use for balance-related declines. | Codes | | ------------------------------------------------- | | `PAY_084` `PAY_499` `PAY_256` `PAY_231` `PAY_016` | #### "This payment exceeds the allowed limit. Please use a smaller amount or try another method." Use for amount limit violations. | Codes | | --------------------------------------------------------------------- | | `PAY_399` `PAY_630` `PAY_515` `PAY_481` `PAY_482` `PAY_444` `PAY_096` | #### "Transaction limit reached. Please try again later or use another method." Use for velocity/frequency limit violations. | Codes | | ------------------------------------------------------------------------------- | | `PAY_484` `PAY_483` `PAY_514` `PAY_513` `PAY_512` `PAY_511` `PAY_510` `PAY_509` | #### "This request has been processed. Please avoid resubmitting the same transaction." Use for duplicate transaction attempts. | Codes | | ----------------------------------------------------------- | | `CAD_011` `PAY_620` `PAY_319` `PAY_318` `PAY_317` `PAY_320` | | `PAY_311` `PAY_310` `PAY_323` `PAY_011` `PAY_009` | #### "Bank details could not be validated. Please confirm the recipient bank information and try again." Use for payout/push errors related to recipient bank data validation. | Codes | | --------------------------------------------------------------------- | | `PAY_650` `PAY_621` `PAY_575` `PAY_574` `PAY_566` `PAY_565` `PAY_560` | | `PAY_590` `PAY_589` `PAY_587` `PAY_586` `PAY_553` `PAY_551` | | `PAY_333` `PAY_325` `PAY_344` `PAY_343` `PAY_342` | | `PAY_349` `PAY_348` `PAY_347` `PAY_346` | #### "Information is missing or invalid. Please review and try again." Use for generic validation errors, missing fields, and malformed requests. | Codes | | --------------------------------------------------------------------------------------------------------------------------------- | | `GE_002` `PAY_065` `PAY_447` `PAY_486` `PAY_274` `PAY_278` `PAY_276` `PAY_275` `PAY_277` | | `PAY_666` `PAY_678` `PAY_519` `PAY_060` `PAY_269` `PAY_271` `PAY_270` `PAY_524` `PAY_532` | | `PAY_212` `PAY_213` `PAY_211` `PAY_210` `PAY_209` `PAY_207` `PAY_206` `PAY_205` `PAY_204` `PAY_203` `PAY_202` `PAY_201` `PAY_200` | | `PAY_214` `PAY_215` `PAY_216` `PAY_217` `PAY_218` `PAY_219` `PAY_220` `PAY_221` `PAY_222` `PAY_223` `PAY_224` `PAY_225` | | `PAY_253` `PAY_252` `PAY_251` `PAY_250` `PAY_249` `PAY_248` `PAY_247` `PAY_246` `PAY_245` | | `PAY_260` `PAY_259` `PAY_267` `PAY_266` `PAY_265` `PAY_264` `PAY_263` `PAY_262` | #### "Can't process this request right now. Please try again later." Use for system errors, timeouts, and temporary failures. | Codes | | ---------------------------------------------------------- | | `GE_001` `PAY_640` `PAY_501` `PAY_505` `PAY_506` `PAY_507` | | `PAY_112` `PAY_281` `PAY_304` `PAY_480` | ### Implementation Example ```javascript const CUSTOMER_MESSAGES = { 'card_data': 'Check if the card information provided is correct.', 'verification': 'Additional verification is required. Please try again and complete verification.', 'issuer_decline': 'Your bank declined this payment. Please contact your bank or try another payment method.', 'fraud_block': "This payment couldn't be processed. Try another payment method or contact support.", 'insufficient': 'Insufficient funds. Please use another payment method or add funds and try again.', 'limit': 'This payment exceeds the allowed limit. Please use a smaller amount or try another method.', 'velocity': 'Transaction limit reached. Please try again later or use another method.', 'duplicate': 'This request has been processed. Please avoid resubmitting the same transaction.', 'bank_validation': 'Bank details could not be validated. Please confirm the recipient bank information and try again.', 'invalid_input': 'Information is missing or invalid. Please review and try again.', 'system_error': "Can't process this request right now. Please try again later.", }; // Map response codes to message categories function getCustomerMessage(responseCode) { // Your mapping logic here β€” see tables above // NEVER return the raw responseCode or internal message to the customer } ``` > **Tip:** Log the full `responseCode` and `message` internally for debugging and support, but only show the mapped customer-friendly message in your UI. *** ## Internal Response Codes Reference The table below is the **complete internal reference** for development and debugging. These codes and descriptions should only appear in server logs, admin dashboards, and support tools β€” never in customer-facing interfaces. #### Response Codes Table
00Payment Approved
GE_001Generic Error
CAD_011External ID already exists
PAY_700Blocked by cardholder/contact cardholder
PAY_699Transaction not supported/blocked by issuer
PAY_680Declined by issuer
PAY_679Bank not found
PAY_678Value provided for one of the fields is too short.
PAY_677TransactionDetail secondaryId must be null.
PAY_676SenderDetail paymentCredentialReference must be null.
PAY_675Recipient card paymentCredentialReference must be null.
PAY_674TaxAmount field in Structured Remittance Information is not supported by this route
PAY_673Unstructured Remittance Information should not be present with Structured Remittance Information in payout
PAY_672Structured remittance information is not supported for payment method 'W'
PAY_671Tax amount is always positive and greater than zero
PAY_670ReferredDocumentInformation data block exceeds the maximum length
PAY_669Tax Amount and Tax Currency Code must be provided together
PAY_668Structural Remittance Information is disabled for this environment
PAY_667Structured Remittance array is larger than supported
PAY_666Value provided for one of the fields has incorrect format
PAY_665Amount in SRI does not equal payout request amount
PAY_664Structural Remittance Information currency code must match recipientDetail.bank.currencyCode for selected route
PAY_663Minimum required structured remittance information is not provided. Amount, Currency code and creditor reference information is required and creditor reference code must be SCOR for the selected route
PAY_662StructuredRemittance data block exceeds the maximum length.
PAY_661StructuredRemittance array not supported for selected route
PAY_660Negative signed amount provided for an invoice
PAY_659Positive signed amount provided for a credit note
PAY_658None of the available routes for this transaction support structured remittance information
PAY_657If structuredRemittance object is included in the payload then a referredDocumentInformation or a creditorReference array must be included
PAY_656If structuredRemittance object is included in the payload then amountCurrencyCode is mandatory
PAY_655If structuredRemittance object is included in the payload then amount is mandatory
PAY_654If creditorReference is provided then code is mandatory
PAY_653If creditorReference is provided then number is mandatory
PAY_652If referredDocumentInformation is provided then number is mandatory
PAY_651If referredDocumentInformation is provided then code is mandatory
PAY_650Recipient bank account cannot be validated. Bank or identity information is missing or incorrect.
PAY_649Payout method does not match the recipient details provided
PAY_648Recipient account identifier phone number not in correct format
PAY_647Only Recipient wallet account identifier type PHONENUMBER supported for this wallet operator
PAY_646IBAN is not enabled on SEPA Instant
PAY_645Route is not allowed.
PAY_644The initiatingPartyId is too long.
PAY_643Transaction not in a cancellable state.
PAY_642The length of the field is incorrect.
PAY_641Additional Data Name under Transaction Details is not in the correct format.
PAY_640Undefined Internal Error. Please contact your Visa representative for assistance.
PAY_639Quote ID is invalid
PAY_638The currency pair associated with the Quote ID does not match the currency pair in the Payout request.
PAY_637Quote ID does not belong to the given Initiating Party ID
PAY_636Quote ID not found
PAY_635Quote ID has expired
PAY_634Initiating Party ID is not correct
PAY_633Recipient tax code (RUC) is required and has not been provided or is not correct
PAY_632Data for Sender country is required to process your payout request
PAY_631Transaction Amount or derived Destination Amount is 0
PAY_630Transaction amount exceeds the transaction limit
PAY_629Sender type supported is Individual for this payout
PAY_628Data for Sender address is required to process your payout request
PAY_627Data for Sender date of birth is required to process your payout request
PAY_626Recipient type supported is Individual for this payout
PAY_625Configuration error. Onboarding incomplete. Please contact your Visa representative.
PAY_624Sender reference number contains special characters.
PAY_623Recipient additional data value contains special characters or exceeds max length allowed.
PAY_622One or more of sender or reciepient identity data is missing or invalid
PAY_621Recipient bank code or bank account number is required to process your payout request
PAY_620This is a duplicate transaction
PAY_619Purpose of payment is not correct
PAY_618Configuration error. Please contact your Visa representative.
PAY_617Data provided for one or more Sender fields is not required for this destination country
PAY_616Sender company registration number is not correct
PAY_615Data for one or more Sender fields are required to process your payout request
PAY_614Data for one or more Sender fields are not correct or contains special characters
PAY_613Duplicate data for one or more Sender identification list fields exists
PAY_612Sender ID number is not correct
PAY_611Only one form of identification is required for the Sender
PAY_610Data for one or more Sender fields contains alpha and/or special characters.
PAY_609Data for one or more Sender fields contains special characters
PAY_608Data for one or more Sender identification list fields is not correct
PAY_607Sender date of birth is not correct
PAY_606Sender type must be 'I' for Individual or 'C' for Company
PAY_605Data for one or more Sender fields is required to process your payout request
PAY_604Sender identity type is mandatory for this route
PAY_603Sender province is not correct/ Sender province or state is not correct
PAY_602Sender email address is not correct
PAY_601Sender ID type is not correct
PAY_600Sender ID issue country is not correct
PAY_599Data for one or more Sender country fields is not correct
PAY_598Data provided for one or more Sender fields is not currently supported
PAY_597Combination of Recipient bank account number and Recipient bank code type is not correct
PAY_596Combination of Recipient bank code and Recipient bank branch code is not correct
PAY_595Recipient bank code is not currently supported for this payout request
PAY_594Combination of Recipient 'IBAN' bank account number and Recipient bank code is not correct
PAY_593Recipient bank account number has failed the modulus check
PAY_592Recipient bank country code does not support IBAN account numbers
PAY_591Recipient bank country code requires IBAN account number to process this payout request
PAY_590Recipient bank BIC is not correct
PAY_589Recipient bank BIC contains special characters.
PAY_588Data for one or more fields are not correct or contains special characters.
PAY_587Recipient bank BIC is not correct
PAY_586Recipient bank BIC is required to process your payout request
PAY_585Remove the Recipient bank account number suffix. The suffix is included in the Recipient bank account number.
PAY_584Recipient bank account number suffix is required to process your payout request
PAY_583Recipient bank account type contains special characters.
PAY_582Recipient bank account type is not correct.
PAY_581Recipient bank account type is required to process your payout request
PAY_580Recipient bank account number suffix contains special characters.
PAY_579Recipient bank account number suffix contains alpha and/or special characters.
PAY_578Recipient bank account number has failed the modulus check.
PAY_577Recipient bank account number contains special characters.
PAY_576Recipient bank account number contains alpha and/or special characters.
PAY_575Recipient bank account number is required to process your payout request.
PAY_574Recipient bank account number is not correct
PAY_573Combination of Recipient bank BIC and Recipient bank code is not correct
PAY_572One or more of the beneficiary bank information fields provided is incorrect
PAY_571Recipient bank branch code is not correct
PAY_570The bank branch code provided conflicts with the rest of the recipient bank information in the payout request.
PAY_569Recipient bank branch code should not be provided for this destination country
PAY_568Recipient bank branch code contains alpha and/or special characters.
PAY_567Recipient bank branch code is required to process your payout request
PAY_566Combination of Recipient bank code and Recipient bank account number is not correct
PAY_565Recipient bank code is not correct
PAY_564The bank code provided conflicts with the rest of the recipient bank information in the payout request.
PAY_563Recipient bank code should not be provided for this destination country
PAY_562Recipient bank code contains alpha and/or special characters.
PAY_561Recipient bank code contains special characters.
PAY_560Recipient bank code is required to process your payout request
PAY_559Recipient bank name is not currently supported for this payout request
PAY_558Recipient bank account name is not correct
PAY_557Recipient bank name contains special characters.
PAY_556Recipient bank name is required to process your payout request
PAY_555Recipient bank account name contains special characters.
PAY_554Recipient bank account name is required to process your payout request.
PAY_553Unable to validate this payout request becuase one or more of recipient bank information provided is missing or invalid.
PAY_552Combination of Recipient bank country code and Recipient bank account number for 'IBAN' bank account number type is not correct
PAY_551Recipient details are not in the correct format. Contact your Visa representative for assistance.
PAY_550Sender details are not in the correct format. Contact your Visa representative for assistance.
PAY_549Destination country is not currently supported
PAY_548Destination route not set up
PAY_547Data provided for one or more Recipient fields is not required for this destination country
PAY_546Duplicate data for one or more Recipient identification list fields exists
PAY_545Only one form of identification is required for the Recipient
PAY_544Data for one or more Recipient identification list fields is not correct
PAY_543Recipient identity type is mandatory for this route
PAY_542Recipient company registration number is not correct
PAY_541Recipient email address is not correct
PAY_540Recipient date of birth is not correct.
PAY_539Recipient ID type is not correct
PAY_538Data for one or more of Recipient country fields is not correct
PAY_537Recipient ID number is not correct
PAY_536Recipient ID issue country is not correct
PAY_535Data provided for one or more Recipient fields is not currently supported
PAY_534Data for one or more Recipient fields are not correct or contains special characters
PAY_533Data for one or more Recipient fields contains alpha and/or special characters
PAY_532The minor units in Transaction Amount does not align with currency exponent.
PAY_531Data for one or more Recipient fields contains special characters
PAY_530Data for one or more Recipient fields contains special characters
PAY_529Recipient province or state is not correct
PAY_528Data for one or more of Recipient fields are required to process your payout request
PAY_527There is an issue with the data of this transaction and it cannot be processed
PAY_526A balance for the currency and account requested does not exist.
PAY_525No balances to return.
PAY_524The currencyCode must be a alphabetic string of length 3.
PAY_523TransactionDetail sourceAmount must be null
PAY_522TransactionDetail sourceCurrencyCode must be null
PAY_521The sourceAmount should not be supplied for Push to Account.
PAY_520The sourceCurrencyCode should not be supplied for Push to Account.
PAY_519Unknown query parameter
PAY_518The number of calendar days requested must be an integer value between 1 and 30.
PAY_517PayoutMethod must not be null
PAY_516Subscriber not authorized to receive transaction
PAY_515Transaction max amount exceeded
PAY_514Monthly recipient velocity limit exceeded
PAY_513Monthly sender velocity limit exceeded
PAY_512Weekly recipient velocity limit exceeded
PAY_511Weekly sender velocity limit exceeded
PAY_510Daily recipient velocity limit exceeded
PAY_509Daily sender velocity limit exceeded
PAY_508Blacklist error
PAY_507MFS system error
PAY_506E-wallet system error
PAY_505Transaction could not be executed
PAY_504Subscriber not authorized to receive amount
PAY_503Subscriber not found
PAY_502Partner corridor not active
PAY_501Transaction Error
PAY_500Expired transfer proposal
PAY_499Insufficient fund in merchant account
PAY_498Sender middleName must be null
PAY_497The idType must be one of CLIENT_REFERENCE_ID or PAYOUT_ID.
PAY_496Payout not found
PAY_495The route or wallet operator does not support the destination amount precision
PAY_494A company cannot have a tax id
PAY_493The additional identity data block contains an invalid item
PAY_492The payoutSpeed is not correct.
PAY_491Business Application ID is not correct
PAY_490Configuration error. Please contact your Visa representative.
PAY_489This payout request could not be processed. Please contact your Visa representative.
PAY_488This payout request could not be processed. Please contact your Visa representative.
PAY_487No route exists for this combination
PAY_486The value provided for one or more request parameters is considered invalid.
PAY_485Payment rejected for compliance related reason at partner. Contact your Visa representative for assistance.
PAY_484Recipient’s transaction frequency exceeds the limit.
PAY_483Sender’s transaction frequency exceeds the limit.
PAY_482Transaction amount exceeds the transaction limit for the recipient.
PAY_481Transaction amount exceeds the transaction limit for the sender
PAY_480This request could not be processed. Please try again.
PAY_479Recipient details validation in progress with partner
PAY_478Sender minimum information is missing
PAY_477A company cannot have a city of birth
PAY_476A company cannot have a foreign id
PAY_475A company cannot have a national id
PAY_474A company cannot have a driving license
PAY_473A company cannot have a passport
PAY_472Recipient name or first name and last name are required.
PAY_471A company cannot have a foreign id
PAY_470A company cannot have a tax id
PAY_469A company cannot have a national id
PAY_468A company cannot have a driving license
PAY_467A company cannot have a passport
PAY_466A company cannot have date of birth or country of birth or city of birth
PAY_465Recipient name or first name and last name are required.
PAY_464Recipient name or first name and last name are required.
PAY_463Recipient wallet currency is not supported for this wallet operator
PAY_462Recipient wallet country is not supported for this wallet operator
PAY_461Recipient identification id type is not supported
PAY_460Recipient wallet account is unavailable
PAY_459Recipient details mismatch at wallet operator.
PAY_458Recipient wallet account not found
PAY_457A company cannot have a country of birth
PAY_456A company cannot have a date of birth
PAY_455An Individual cannot have a legal registration number
PAY_454Sender age is not supported
PAY_453This combination of sender type and recipient type provided is not supported for this route
PAY_452A company cannot have date of birth or country of birth or city of birth
PAY_451Either senderAccountNumber or senderReferenceNumber is required
PAY_450One of sender name or first name and last name must be provided
PAY_449Sender name or first name and last name are required.
PAY_448Country is not currently supported
PAY_447Value provided for one of the fields is invalid
PAY_446Sender-beneficiary relationship is not allowed
PAY_445Value provided for one of the fields is too long
PAY_444Payment value less than the minimum route limit
PAY_443The sender identification idOwnerType must be null.
PAY_442Sender middleName must be null
PAY_441No eligible routes found which allow payer unstructured identity
PAY_440The sender sourceOfFunds field must be null.
PAY_439SenderReferenceNumber field must be null
PAY_438Only sender name or firstName + lastName combination is allowed
PAY_437Sender name or firstName + lastName combination is required
PAY_436Only sender firstName + lastName combination or name is allowed
PAY_435Sender firstName + lastName combination or name is required
PAY_434Only sender name is allowed
PAY_433Sender name is required
PAY_432Only senderAccountNumber or senderReferenceNumber is allowed
PAY_431SenderAccountNumber or senderReferenceNumber is required
PAY_430Only sender firstName + lastName combination or fullName is allowed
PAY_429Sender firstName + lastName combination or fullName is required
PAY_428Only sender companyName or fullName is allowed
PAY_427Sender companyName or fullName is required
PAY_426The transaction currency should be same as either the client's settlement currency or recipient's wallet currency
PAY_425The transaction currency should be same as either the client's settlement currency or recipient's bank currency
PAY_424The minor units in structuredRemittance taxAmount does not align with currency exponent
PAY_423The minor units in structuredRemittance Amount does not align with currency exponent
PAY_422TransactionDetail structuredRemittance amount has invalid content
PAY_421TransactionDetail structuredRemittance taxAmount is invalid
PAY_420TransactionDetail structuredRemittance creditorReference number is too long
PAY_419TransactionDetail structuredRemittance creditorReference number is too short
PAY_418TransactionDetail structuredRemittance creditorReference number is missing
PAY_417TransactionDetail structuredRemittance creditorReference code has invalid content
PAY_416TransactionDetail structuredRemittance creditorReference code has invalid length
PAY_415TransactionDetail structuredRemittance creditorReference code is missing
PAY_414TransactionDetail structuredRemittance referredDocumentInformation relatedDate has invalid content
PAY_413TransactionDetail structuredRemittance referredDocumentInformation number is too long
PAY_412TransactionDetail structuredRemittance referredDocumentInformation number is too short
PAY_411TransactionDetail structuredRemittance referredDocumentInformation number is missing
PAY_410TransactionDetail structuredRemittance referredDocumentInformation code has invalid content
PAY_409TransactionDetail structuredRemittance referredDocumentInformation code has invalid length
PAY_408TransactionDetail structuredRemittance referredDocumentInformation code is missing
PAY_407TransactionDetail structuredRemittance referredDocumentInformation is too long
PAY_406TransactionDetail structuredRemittance taxCurrencyCode is Invalid
PAY_405TransactionDetail structuredRemittance amountCurrencyCode is Invalid
PAY_404TransactionDetail structuredRemittance amountCurrencyCode is missing
PAY_403TransactionDetail structuredRemittance amount is missing
PAY_402TransactionDetail structuredRemittance is too long
PAY_401Transaction endToEndId must not be null
PAY_400Transaction purpose of payment is required
PAY_399Payment amount exceeds the route limit.
PAY_398The transactionDetail transactionCurrencyCode must be a numeric of length 3.
PAY_397The recipient middleName must be null.
PAY_396The recipient identification idOwnerType must be null.
PAY_395The recipientDetail address minorSubDivisionCode must be null.
PAY_394The recipientDetail address streetName must be null.
PAY_393The recipientDetail address streetName must be null.
PAY_392Recipient wallet must be null.
PAY_391The recipient bank must be null.
PAY_390Recipient middleName must be null
PAY_389Only recipient firstName + lastName combination or name is allowed
PAY_388Recipient firstName + lastName combination or name is required
PAY_387Only recipient name is allowed
PAY_386Recipient name is mandatory
PAY_385Only recipient name or firstName + lastName combination is allowed
PAY_384Recipient name or firstName + lastName combination is required
PAY_383No eligible routes found which allow payer unstructured identity
PAY_382Recipient contact number is mandatory for this route.
PAY_381The recipient card must be null.
PAY_380Identity Type of beneficiary is required.
PAY_379Conflicting recipient name, identity or type
PAY_378The recipient payout method is not supported.
PAY_377The recipient payoutMethod must not be null.
PAY_376The recipient alias must be null.
PAY_375Recipient identificationList must be provided
PAY_374The recipient additional data key is not known.
PAY_373At least one recipient additional data item must be supplied
PAY_372Recipient bank BIC is not supported
PAY_371Recipient bank BIC country does not match the SWIFT BIC country
PAY_370Recipient bank BIC contains non-alphanumeric characters
PAY_369Recipient bank BIC has not been supplied and is required in the territory
PAY_368Recipient bank BIC supplied contradicts the bank identified by the supplied bank code
PAY_367Recipient bank BIC does not exist in bank partner reference table
PAY_366Recipient bank BIC country does not match bank account country
PAY_365Recipient bank BIC code contradicts with bank code.
PAY_364The recipient bank account number suffix is required in the territory.
PAY_363Recipient bank account type is required in the territory
PAY_362Recipient bank account IBAN supplied implies a country different than the country code supplied
PAY_361Recipient bank Account Number and Sort Code mismatch
PAY_360Recipient bank IBAN contradicts account number suffix
PAY_359Recipient bank account IBAN contradicts the supplied account number
PAY_358Recipient bank account IBAN contradicts the supplied routing number
PAY_357Recipient bank account IBAN is contradicting
PAY_356Recipient bank account IBAN failed modulus check
PAY_355Recipient bank account IBAN country is not recognized
PAY_354Recipient bank account IBAN is required in the territory
PAY_353Recipient bank account number failed modulus check
PAY_352recipient bank account number prefix contains non-numeric characters
PAY_351recipient bank account number prefix is too short
PAY_350recipient bank account number prefix is too long
PAY_349Recipient bank branch code not found on CB.Net lookup
PAY_348Recipient bank branch code is contradicting
PAY_347Recipient bank branch code should not be supplied for this territory
PAY_346Recipient bank branch code is required in the territory
PAY_345ABA routing number and a Fedwire code have been supplied which contradict each other
PAY_344Recipient bank code is not found on CB.Net lookup
PAY_343Recipient bank ABA routing number contradicts the one derived from this account
PAY_342The recipient bank ABA routing number is required.
PAY_341Recipient bank sort code is not eligible for local schemes
PAY_340Recipient bank sort code is required
PAY_339The recipient bank sort code contains non-alphanumeric characters.
PAY_338The recipient bank sort code contains non-numeric characters.
PAY_337Recipient bank code contradicts the supplied account number
PAY_336Recipient bank code is contradicting
PAY_335Recipient bank code should not be supplied for this territory
PAY_334Selected payment rail is not configured. Please contact your Visa representative.
PAY_333The recipient bank information is not sufficient.
PAY_332Only recipient firstName + lastName combination or fullName is allowed.
PAY_331Recipient firstName + lastName combination or fullName is required.
PAY_330Only recipient companyName or fullName is allowed.
PAY_329Recipient companyName or fullName is mandatory.
PAY_328Only recipient companyName or firstName + lastName combination or fullName is allowed.
PAY_327Recipient companyName or firstName + lastName combination or fullName is required.
PAY_326Recipient bank BIC is not found.
PAY_325Insufficient bank data supplied. Please contact customer support for more info.
PAY_324Transaction not in a cancellable state.
PAY_323This transaction has already been cancelled. Current request is a duplicate.
PAY_322Payout in queue. Wait for the status notification and retry cancellation later.
PAY_321Payout attempt terminated unsuccessfully.
PAY_320Prior payout exists with the same ClientReferenceID but with different key transaction data elements.
PAY_319Current transaction is a duplicate.
PAY_318This payout request is inconsistent with the previously processed payout transaction.
PAY_317Current transaction is a duplicate. Original Payout attempt terminated unsuccessfully.
PAY_316Current transaction is a duplicate. Original Payout attempt is in an undefined state. Please contact a Visa representative.
PAY_315Current transaction is a duplicate. Original Payout attempt is in ERROR. Please contact a Visa representative.
PAY_314Payout not cancellable at this time. Payout is in an inconsistent state.
PAY_313The payout transaction is in an inconsistent state. Please contact your Visa Representative.
PAY_312The payout transaction that has been queried is in an inconsistent/unknown state.
PAY_311This payout request is inconsistent with the previously processed payout transaction.
PAY_310This payout request matches with other payout transactions which are in an inconsistent state. Please contact your Visa Representative.
PAY_309Transaction currency did not match the settlement currency or the destination currency.
PAY_308Payout not cancellable.
PAY_307Transaction currency is not same as Destination currency.
PAY_306Validation failed for Retrieval Reference Number. This transaction can no longer be retried.
PAY_305Final state of the transaction is unknown.
PAY_304Transaction cannot be processed at this time, please contact Visa.
PAY_303Payout is pending cancellation.
PAY_302Payout is not cancellable.
PAY_301Recipient bank account is inactive.
PAY_300Payout request has an illegal character data.
PAY_299Recipient bank account has duplicate data.
PAY_298Additional data is required for this purpose of payment
PAY_297Sender or recipient detail data is incomplete
PAY_296Sender or recipient details exceeds max length.
PAY_295Value contains non-supported characters.
PAY_294Sender or recipient additional identity data contains more than one item of the same name
PAY_293Recipient identification information is invalid or missing
PAY_292Only one document number must be supplied for sender or recipient.
PAY_291Sender or recipient identity number is not numeric.
PAY_290Sender or recipient identity number is not binary.
PAY_289Sender or recipient identity issue country is not allowed.
PAY_288Sender or recipient country in address is not allowed.
PAY_287Sender or recipient identification number is not numeric.
PAY_286Sender or recipient identification type is required.
PAY_285Wallet Operator does not support recipient verification functionality.
PAY_284Route not supported, please contact Visa representative
PAY_283The beneficiary Bank Account Intermediary Account supplied field contains not supported characters.
PAY_282Recipient bank account is not supported.
PAY_281Transaction cannot be processed at this time, please contact Visa.
PAY_280The recipient country and currency route selected is not supported.
PAY_279The recipient country and currency route selected is not supported.
PAY_278Value provided for one of the fields is invalid.
PAY_277Value provided for one of the fields has incorrect format.
PAY_276Value provided for one of the fields is too short.
PAY_275Value provided for one of the fields is too long.
PAY_274Mandatory value is missing.
PAY_273Mismatch between FX quote currency codes and transaction currency codes.
PAY_272Fx is required for this transaction
PAY_271Invalid or unsupported country code
PAY_270Invalid person type provided
PAY_269Address information is required and cannot be empty
PAY_268User with username already exists
PAY_267Agent ID cannot be null or blank
PAY_266Password cannot be null or blank
PAY_265Last name cannot be null or blank
PAY_264First name cannot be null or blank
PAY_263Email cannot be null or blank
PAY_262Password cannot be null or blank
PAY_261User with username does not exist
PAY_260Username cannot be blank
PAY_259Tenant cannot be blank
PAY_258Bank not allowed to transaction
PAY_257Invalid Bank Code
PAY_256Insufficient balance
PAY_255Card information is required
PAY_254Bank account details are required
PAY_253Account number is required
PAY_252Routing number is required
PAY_251Account holder information is required
PAY_250Account type is required
PAY_249A valid bank account type is required. Allowed values: savings, checking, loan, business_checking, or business_saving
PAY_248A valid account holder type is required. Allowed values: personal or business
PAY_247The "companyLegalName" field is required when the account holder is a business
PAY_246Both "firstName" and "lastName" fields are required when the account holder is an personal
PAY_245Invalid source type. Accepted values are "CARD" or "BANK_ACCOUNT".
PAY_244The asset type is required for wallet creation.
PAY_243The owner ID is required for wallet creation.
PAY_242The beneficiary ID is required for wallet creation.
PAY_241The wallet type is required for wallet creation.
CAD_0207Payment Services is required
CAD_0206Payment Methods is required
CAD_0205Country is required
CAD_0204Agent Trade Name is required
CAD_0203Agent Commercial Name is required
CAD_0202Agent Referer is required
CAD_0201Agent id is required
CAD_0200Phone is required
CAD_0199Email is required
PAY_231Insufficient balance to pull transaction
CAD_015Agent Id already exists
PAY_229No payment routes were found
PAY_228Invalid Pix key. Please check and try again.
PAY_227A Pix key is required.
PAY_226A Pix account must be provided to receive the transfer.
PAY_225The recipient country code (Alpha-3) is required for PUSH transactions.
PAY_224The recipient object is required for a PUSH transaction.
PAY_223The sender amount is required for PUSH transactions.
PAY_222The exchange rate must be greater than zero.
PAY_221Invalid currency code for the recipient country. Please check and try again.
PAY_220Invalid currency code for the customer country. Please check and try again.
PAY_219The recipient country code (Alpha-3) is required for PUSH transactions.
PAY_218The customer country code (Alpha-3) is required for PULL transactions.
PAY_217A destination account is required for PUSH transactions.
PAY_216A source account is required for PULL transactions.
PAY_215An amount is required for the transaction.
PAY_214Invalid payment type. Accepted values are "PULL" or "PUSH".
PAY_213Invalid page number.
PAY_212Invalid page size.
PAY_211The "to" date must be provided.
PAY_210The "from" date must be provided.
PAY_209The wallet ID must be in UUID v4 format.
PAY_208The transfer amount must be greater than zero.
PAY_207The destination wallet ID must be a valid UUID v4.
PAY_206The source wallet ID must be a valid UUID v4.
PAY_205An external ID is required.
PAY_204Invalid transfer amount.
PAY_203The destination wallet ID is required.
PAY_202The source wallet ID is required.
PAY_201The wallet ID is required.
PAY_200A valid wallet payload is required.
PAY_199The wallet type is required for wallet creation.
PAY_198The beneficiary ID is required for wallet creation.
PAY_197The owner ID is required for wallet creation.
PAY_196The asset type is required for wallet creation.
PAY_195Invalid source type. Accepted values are "CARD" or "BANK_ACCOUNT".
PAY_194Both "firstName" and "lastName" fields are required when the account holder is an personal
PAY_193The "companyLegalName" field is required when the account holder is a business
PAY_192A valid account holder type is required. Allowed values: personal or business
PAY_191A valid bank account type is required. Allowed values: savings, checking, loan, business_checking, or business_saving
PAY_190Account type is required
PAY_189Account holder information is required
PAY_188Routing number is required
PAY_187Account number is required
PAY_186Bank account details are required
PAY_185Card information is required
1Payment Pending
PAY_183Invalid Phone Number
PAY_182Invalid country code
PAY_181Invalid Account Holder Type
PAY_180Invalid Account Type
PAY_155ACS ERROR - Data exchange response not found by paymentId
PAY_154ACS ERROR - Data exchange not found by requestId
PAY_153ACS ERROR - Data exchange not found by paymentId
PAY_152ACS ERROR - Data exchange not found JWT by tenant and paymentId
PAY_151ACS ERROR - Data exchange update invalid
PAY_150ACS ERROR - Lookup response data not found find by paymentId
PAY_149ACS ERROR - Lookup data not found find by paymentId and transactionId
PAY_148ACS ERROR - Lookup data not found find by paymentId
PAY_147ACS ERROR - Lookup data not found find by tenant and transactionId
PAY_146Declined by 3DS Secure Code
PAY_145Invalid cardholder - High Risk
PAY_144Invalid cardholder - Email
PAY_143Invalid cardholder name
PAY_142Invalid Ip Address
PAY_141Decline blacklist restrictions - Customer
PAY_140Decline blacklist restrictions - IP
PAY_139Decline blacklist restrictions - Card
PAY_131Unable to go online; offline-declined
PAY_130Revocation of all authorizations order
PAY_128Transaction does not qualify for Visa PIN
PAY_127Revocation of authorization order
PAY_126Stop Payment Order
PAY_125Card Authentication failed
PAY_124Denied PIN changeβ€”requested PIN unsafe
PAY_123Denied PIN unblockβ€”PIN change or unblock request declined by issuer
PAY_122Transaction amount exceeds preauthorized approval amount
PAY_121Decline for CVV2 failure
PAY_120Ineligible for resubmission
PAY_119Cash request exceeds issuer or approved limit
PAY_118Cash service not available
PAY_117Force STIP
PAY_116Surcharge amount not supported by debit network issuer.
PAY_115Surcharge amount not permitted on Visa cards or EBT food stamps (U.S. acquirers only)
PAY_114Verification data failed
PAY_113Additional customer authentication required
PAY_112System malfunction
PAY_111Transaction cannot be completed - violation of law
PAY_097PIN data required
PAY_098Different value than that used for PIN encryption errors
PAY_110Financial institution or intermediate network facility cannot be found for routing (receiving institution ID is invalid)
PAY_109Issuer or switch inoperative and STIP not applicable or not available for this transaction; Timeout
PAY_108Ineligible to receive financial position information (GIV)
PAY_107Cannot verify PIN; for example, no PVV
PAY_106No reason to decline a request for address verification, CVV2 verification, or a credit voucher or merchandise return
PAY_105Negative CAM, dCVV, iCVV, or CVV results
PAY_104Cryptographic error found in PIN
PAY_103No financial impact
PAY_102Already reversed (by Switch)
PAY_101Blocked, first usedβ€”Transaction from new cardholder, and card not properly unblocked
PAY_100Unsolicited reversal
PAY_099Allowable number of PIN entry tries exceeded
PAY_081Lost card, pick up (fraud account)
PAY_096Exceeds withdrawal frequency limit
PAY_095Transaction does not fulfill AML requirement
PAY_094Security violation (source is not correct issuer)
PAY_093Restricted card (card invalid in this region or country)
PAY_092Exceeds approval amount limit
PAY_091Suspected fraud
PAY_090Transaction not allowed at terminal
PAY_089Transaction not permitted to cardholder
PAY_088Incorrect PIN or PIN missing
PAY_087Expired card or expiration date is missing
PAY_086No savings account
PAY_085No checking account
PAY_084Not sufficient funds
PAY_083Closed account
PAY_082Stolen card, pick up (fraud account)
PAY_080No credit account
PAY_079File temporarily not available for update or inquiry
PAY_078Unable to locate record in file
PAY_077No action taken
PAY_076Re-enter transaction
PAY_075No such issuer
PAY_074Invalid account number (no such number)
PAY_073Invalid amount or currency conversion field overflow
PAY_072Invalid transaction
PAY_071Approved (V.I.P)
PAY_070Partial approval
PAY_069Pick up card, special condition (fraud account)
PAY_068Error
PAY_067Do not honor
PAY_132Pick up card (no fraud)
PAY_133Invalid merchant
PAY_064Refer to card issuer, special condition
PAY_063Refer to card issuer
PAY_062Business Operation is Invalid
GE_008Notification not found
GE_007Notification already exists
GE_006Authentication method is invalid
GE_005Event is invalid
GE_004Bearer authentication requires username, a password and url token authentication
GE_003Basic authentication requires both a username and a password
PAY_066Transaction rejected by gateway analysis
PAY_065Invalid request
PAY_061Provider Requested Error
CAD_006Tokenization card rejected by rules
GE_002Parameter is invalid
PAY_060Invalid external id format
SE_003Invalid header token
PAY_059AVS partial approved
PAY_058AVS does not match
PAY_057AVS unknow
PAY_056Email verification failed
PAY_055Email verification unknown
PAY_054Telephone verification failed
PAY_053Telephone verification unknown
PAY_052Postal code verification failed
PAY_051Postal code verification unknown
PAY_050Cardholder name verification failed
PAY_049Cardholder name verification unknown
PAY_048Address verification failed
PAY_047Address verification unknown
PAY_046CVV does not match
PAY_045CVV unkown
PAY_044Revocation of all authorizations order
PAY_043Revocation of authorization order
PAY_042Rejected by card issuer
PAY_041Security code invalid
PAY_040Security Rules Violated
PAY_039Unknown card
PAY_038Card blocked
PAY_037Restricted card
PAY_036Illegal transaction
PAY_035Invalid security code
PAY_034Limit exceeded
PAY_033Stolen card, pick up
PAY_032Lost card
PAY_031Security code expired
PAY_030Fraud suspicion
PAY_029Card expired
PAY_028Impossible Reference Number
PAY_027Access denied
PAY_026Annulation by client
PAY_025Invalid card issuer
PAY_024Invalid requested amount
PAY_023Hold card
PAY_022Rejected by provider
SE_002Invalid header token
CARD_010Invalid request
PAY_021Invalid currency code
PAY_020Amount cannot be less or equal to zero
PAY_019Invalid IP Address
PAY_018Capture amount cannot be negative
PAY_017Capture amount cannot be greater than the original amount
PAY_015Invalid currency
PAY_014No payment found to void
PAY_013No payment found to void
PAY_012No payment found to refund with external payment id
PAY_011Payment duplicated for externalPaymentId
PAY_010No payment founded to capture with external payment id
PAY_009Payment with external id already captured
PAY_008Country not found
PAY_007There isn't parameter to agent and event
PAY_006Payment method ID is null for description
PAY_005Payment method not found for description
PAY_004Credential not found
PAY_003Key not found for agent
PAY_002Agent not found
PAY_001No router found for agent and payment method
CA_007Card not found, invalid token
CA_006Card not found, invalid previous payment code
PAY_016Insufficient balance to refund
SE_001Unauthorized request
CAD_005Secure code is invalid
CAD_004Expiration date is invalid
CAD_003Invalid cardholder name
CAD_002Pan is invalid
CAD_001Origin not allowed
--- description: >- The Inyo Gateway tracks payments through well-defined lifecycle statuses. --- # Payment Status ## Status Lifecycle ``` CHALLENGE ──→ AUTHORIZED ──→ CAPTURED ──→ REFUNDED β”‚ β”‚ β”‚ ↓ ↓ ↓ DECLINED VOIDED PARTIALLY_REFUNDED PARTIALLY_CAPTURED ``` ## Status Reference | Status | Description | Next Actions | |---|---|---| | **CHALLENGE** | 3DS verification required β€” cardholder must complete authentication | Wait for `AUTHORIZED` or `DECLINED` | | **AUTHORIZED** | Payment approved, funds reserved on the card | [Capture](../payment/pulling-funds/cards/capture.md) or [Void](../payment/pulling-funds/cards/void.md) | | **CAPTURED** | Funds settled to merchant account | [Refund](../payment/pulling-funds/cards/refund.md) (full or partial) | | **PARTIALLY_CAPTURED** | A portion of the authorized amount was captured | [Refund](../payment/pulling-funds/cards/refund.md) the captured portion | | **VOIDED** | Authorization cancelled before capture β€” funds released to cardholder | Terminal state | | **REFUNDED** | Full captured amount returned to cardholder | Terminal state | | **PARTIALLY_REFUNDED** | A portion of the captured amount was refunded | Additional [refunds](../payment/pulling-funds/cards/refund.md) possible | | **DECLINED** | Payment rejected by issuer, network, or fraud rules | Retry with different method | ## Key Rules - **AUTHORIZED β†’ VOIDED**: Only before capture. Voids release the hold on the card. - **AUTHORIZED β†’ CAPTURED**: Must happen within 7 days, or the authorization expires and is auto-voided. - **CAPTURED β†’ REFUNDED**: Can be full or partial. Multiple partial refunds are allowed. - **DECLINED**: Terminal β€” the payment cannot be retried with the same `externalPaymentId`. # Test Data Sandbox test data for simulating different transaction scenarios: - [**Test Cards**](cards.md) β€” Card numbers for approvals, declines, 3DS, AVS, and CVV testing # Cards Test cards - Without 3DS | Number | Description | | ---------------- | ---------------- | | 5163613613613613 | Mastercard Debit | | 5555555555554444 | Mastercard | | 5454545454545454 | Mastercard | | 2221000000000009 | Mastercard | | 4462030000000000 | Visa Debit | | 4444333322221111 | Visa | | 4911830000000 | Visa | | 4917610000000000 | Visa | | 343434343434343 | Amex | | 6011000400000000 | Discover | | 36700102000000 | Diners | Test cards - With 3DS | Number | Description | | ---------------- | ---------------- | | 5413330033003303 | Mastercard Debit | | 5169527513596963 | Mastercard | | 4462030000000000 | Visa Debit | | 4983305199046950 | Visa | | 6011000400000000 | Discover | Test cards - With Data-Only 3DS | Number | Description | | ---------------- | ----------- | | 5454545454545454 | Mastercard | | 4975303994654672 | Visa | | 6011926557021045 | Discover | In order to simulate acquirer authorization error codes and validations, please refer to the table: | Name | Result | | ---------- | --------------------------------------------- | | AUTHORISED | HTTP 200 | | REFUSED | HTTP 400 | | ERROR | HTTP 400 | | REFUSED4 | HTTP 400, Hold card | | REFUSED5 | HTTP 400, General refuse | | REFUSED13 | HTTP 400, Invalid amount | | REFUSED33 | HTTP 400, Card Expired | | REFUSED34 | HTTP 400, Suspicious activity, possible fraud | | REFUSED41 | HTTP 400, Lost Card | | REFUSED43 | HTTP 400, Stolen Card | | REFUSED55 | HTTP 400, Invalid CVV | | REFUSED76 | HTTP 400, Card blocked | | REFUSED85 | HTTP 400, Rejected by the card issuer | CVV and AVS test values for Mastercard/Visa/Diners/Discover | Name | Result | | --------- | -------------------------------------------- | | < empty > | B - CVV/CVC not supplied by shopper/merchant | | 111 | C - CVV/CVC not checked | | 222 | C - CVV/CVC not checked | | 333 | C - CVV/CVC not checked | | 444 | D - CVV/CVC not matched | | 555 | A - CVV/CVC matched | CVV and AVS test values for Amex | Name | Result | | --------- | -------------------------------------------- | | < empty > | B - CVV/CVC not supplied by shopper/merchant | | 1111 | C - CVV/CVC not checked | | 2222 | C - CVV/CVC not checked | | 3333 | C - CVV/CVC not checked | | 4444 | D - CVV/CVC not matched | | 5555 | C - CVV/CVC not checked | | 6666 | A - CVV/CVC matched | AVS (Address verification system), that matches cardholder billing information | Name | Result | Description | | ------------- | ------------------------- | ----------------------------------------------------- | | AAAA or AAAAA | APPROVED | Postcode and address matched | | BBBB or BBBBB | PARTIAL APPROVED | Postcode matched; address not checked | | CCCC or CCCCC | PARTIAL APPROVED | Postcode matched; address not matched | | DDDD or DDDDD | PARTIAL APPROVED | Address matched; postcode not checked | | EEEE or EEEEE | NOT SENT TO ACQUIRER | Postcode and address not checked | | FFFF or FFFFF | PARTIAL APPROVED | Address matched; postcode not matched | | GGGG or GGGGG | PARTIAL APPROVED | Postcode not checked; address not matched | | HHHH or HHHHH | NOT SUPPLIED BY SHOPPER | Postcode and address not supplied by shopper/merchant | | IIII or IIIII | PARTIAL APPROVED | Address not checked; postcode not matched | | JJJJ or JJJJJ | FAILED | Postcode and address not matched | | < empty > | NOT SUPPLIED BY SHOPPER | Postcode and address not supplied by shopper/merchant | | KKKK or KKKKK | NO RESPONSE FROM ACQUIRER | Postcode and address not checked | | LLLL or LLLLL | NOT CHECKED BY ACQUIRER | Postcode and address not checked | | MMMM or MMMMM | UNKNOWN | Postcode and address not checked | # Remittances The Remittances API provides a complete, end-to-end solution for building cross-border payment applications. It combines Inyo's **Core Services** (regulatory infrastructure and money transmission licensing), **Compliance Platform** (KYC, KYB, AML, and anti-fraud), and **Payment Gateway** (fund collection and disbursement) into a single, unified integration. *** ### How It Works A remittance transaction follows a well-defined lifecycle that ensures regulatory compliance at every step: ``` β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Sender β”‚ β”‚ Compliance β”‚ β”‚ Quote β”‚ β”‚ Recipient β”‚ β”‚ Onboarding │───▢│ Verification │───▢│ (FX Rate) │───▢│ Setup β”‚ β”‚ (KYC) β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ Receipt β”‚ β”‚ Transaction β”‚ β”‚ Funding β”‚β—€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ (Regulated) │◀───│ Execution │◀───│ Source β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ (Card/ACH) β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ``` *** ### Integration Steps | Step | Action | Endpoint | Description | | ---- | ------ | -------- | ----------- | | 1 | [Sender Onboarding](sender/) | `POST /people` | Create the sender and trigger KYC/AML screening | | 2 | [Compliance Check](sender/trust-level-limits.md) | `GET /participants/{id}/complianceLevels` | Verify the sender has reached at least Level 1 | | 3 | [Destinations](data-population/payout-countries-enabled.md) | `GET /payout/{country}/destinations` | Fetch available payout corridors | | 4 | [Get a Quote](quotes-fx.md) | `POST /payout/quotes` | Lock in FX rate and fees | | 5 | [Recipient Setup](recipient.md) | `POST /people` | Create the beneficiary using country-specific schemas | | 6 | [Recipient Account](recipient-account.md) | `POST /payout/participants/{id}/recipientAccounts/gateway` | Link the recipient's bank account | | 7 | [Funding Source](sender-funding-account.md) | `POST /payout/participants/{id}/fundingAccounts` | Register the sender's card or ACH account | | 8 | [Limits Check](sender/trust-level-limits.md) | `GET /fx/participants/{id}/limits` | Verify the sender hasn't exceeded transaction limits | | 9 | [Execute Transaction](transaction.md) | `POST /fx/transactions` | Submit the transaction with all linked IDs | | 10 | [Webhooks](webhooks.md) | `POST /webhooks` | Register for real-time status notifications | *** ### Architecture Pattern Inyo requires a **Backend-for-Frontend (BFF)** architecture. Your API credentials (`x-api-key`, `x-agent-id`, `x-agent-api-key`) must **never** be exposed to frontend or mobile clients. ``` β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Frontend │────▢│ Your Server │────▢│ Inyo API β”‚ β”‚ (React, etc) β”‚ β”‚ (BFF Layer) β”‚ β”‚ (Sandbox / β”‚ β”‚ │◀────│ │◀────│ Production) β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ Holds API keys KYC, FX, Payout Orchestrates calls Compliance engine ``` *** ### Caching Guidelines The Inyo API enforces rate limits on data-population endpoints. Implement caching as follows: | Data | Cache Duration | Reason | | ---- | -------------- | ------ | | Destinations & banks | 24 hours | Rarely change | | Compliance limits | 24 hours (or until a transaction occurs) | Semi-static | | Recipient/account schemas | 24 hours | Rarely change | | Quotes | **Never cache** | Expire within ~2 minutes | *** ### Transaction Types The platform supports three transaction types: | Type | Description | Use Case | | ---- | ----------- | -------- | | `FX` | Cross-border foreign exchange transaction | Remittances, international payouts | | `TOP_UP` | Airtime or mobile top-up | Telecom recharges | | `BILLPAY` | Bill payment | Utility and service payments | *** ### Need Help? * **Reference implementation**: [Remittance Sample Project](https://github.com/inyo-global/remittance-sample) β€” a working Node.js + React app demonstrating the full integration * **OpenAPI specification**: [Interactive API docs](https://dev-api.inyoglobal.com/sandbox/) * **Support**: Get in touch with our sales team # Getting Started This guide walks you through sending your first cross-border transaction on the Inyo sandbox. By the end, you'll have a working remittance from a US sender to an international recipient. *** ### Prerequisites Before you begin, ensure you have: * **Sandbox credentials** provided by Inyo during onboarding: * `tenant` β€” Your organization identifier * `x-api-key` β€” Primary API key (Tenant-level) * `x-agent-id` β€” Your agent UUID * `x-agent-api-key` β€” Agent-level API key * **Node.js v18+** (if running the [sample project](https://github.com/inyo-global/remittance-sample)) * A REST client (cURL, Postman, or similar) > πŸ“¬ Don't have credentials yet? Get in touch with our sales team to request sandbox access. *** ### Sandbox Environment | Resource | URL | | ------------------ | ---------------------------------------------------------------------------------- | | Core API (sandbox) | `https://api.sandbox.inyoplatform.com` | | OpenAPI docs | [https://dev-api.inyoglobal.com/sandbox/](https://dev-api.inyoglobal.com/sandbox/) | | Developer portal | [https://dev.inyoglobal.com](https://dev.inyoglobal.com) | *** ### Quick Start: Your First Transaction This walkthrough covers the minimum steps to execute a USD β†’ BRL remittance in the sandbox. #### Step 1: Create the Sender Register the person who will send money. This triggers KYC/AML screening. ```bash curl --request POST \ --url https://api.sandbox.inyoplatform.com/organizations/$TENANT/people \ --header 'Content-Type: application/json' \ --header "x-api-key: $API_KEY" \ --data '{ "firstName": "John", "lastName": "Doe", "email": "john.doe@example.com", "birthDate": "1990-01-15", "phoneNumber": "+15551234567", "gender": "Male", "address": { "countryCode": "US", "stateCode": "CA", "city": "San Francisco", "line1": "123 Market St", "zipcode": "94105" }, "documents": [ { "type": "SSN", "document": "123456789", "countryCode": "US" } ] }' ``` > ⚠️ **Save the returned `id`** β€” this is the `senderId` used throughout the transaction lifecycle. #### Step 2: Verify Compliance Level Confirm the sender has reached at least **Level 1** before proceeding. ```bash curl --request GET \ --url https://api.sandbox.inyoplatform.com/organizations/$TENANT/participants/$SENDER_ID/complianceLevels \ --header "x-agent-id: $AGENT_ID" \ --header "x-agent-api-key: $AGENT_KEY" ``` If the response shows `LEVEL_0`, the sender is missing required fields. Check the `nextLevel.requiredFields` array to see what's needed. #### Step 3: Get Available Destinations ```bash curl --request GET \ --url https://api.sandbox.inyoplatform.com/organizations/$TENANT/payout/us/destinations \ --header "x-agent-id: $AGENT_ID" \ --header "x-agent-api-key: $AGENT_KEY" ``` This returns the list of countries your tenant is enabled to send to, along with their currencies. #### Step 4: Lock in an FX Quote ```bash curl --request POST \ --url https://api.sandbox.inyoplatform.com/organizations/$TENANT/payout/quotes \ --header 'Content-Type: application/json' \ --header "x-agent-id: $AGENT_ID" \ --header "x-agent-api-key: $AGENT_KEY" \ --data '{ "fromCurrency": "USD", "toCurrency": "BRL", "amount": 100.00 }' ``` > ⚠️ **Save the `quoteId`** from the response. Quotes expire in \~2 minutes. #### Step 5: Create the Recipient First, fetch the required schema for the destination country: ```bash curl --request GET \ --url https://api.sandbox.inyoplatform.com/organizations/$TENANT/payout/recipients/schema/br \ --header "x-agent-id: $AGENT_ID" \ --header "x-agent-api-key: $AGENT_KEY" ``` Then create the recipient using the schema-required fields: ```bash curl --request POST \ --url https://api.sandbox.inyoplatform.com/organizations/$TENANT/people \ --header 'Content-Type: application/json' \ --header "x-api-key: $API_KEY" \ --data '{ "firstName": "Maria", "lastName": "Silva", "phoneNumber": "+5511999998888", "documents": [ { "type": "CPF", "document": "12345678901", "countryCode": "BR" } ] }' ``` > ⚠️ **Save the returned `id`** β€” this is the `recipientId`. #### Step 6: Link the Recipient's Bank Account Fetch the account schema, then link the bank account: ```bash curl --request POST \ --url https://api.sandbox.inyoplatform.com/organizations/$TENANT/payout/participants/$RECIPIENT_ID/recipientAccounts/gateway \ --header 'Content-Type: application/json' \ --header "x-agent-id: $AGENT_ID" \ --header "x-agent-api-key: $AGENT_KEY" \ --data '{ "externalId": "recipient-acct-001", "asset": "BRL", "payoutMethod": { "type": "BANK_DEPOSIT", "countryCode": "BR", "bankCode": "001", "routingNumber": "0001", "accountNumber": "123456", "accountType": "CHECKING" } }' ``` > ⚠️ **Save the returned `id`** β€” this is the `recipientAccountId`. #### Step 7: Register the Sender's Funding Source ```bash curl --request POST \ --url https://api.sandbox.inyoplatform.com/organizations/$TENANT/payout/participants/$SENDER_ID/fundingAccounts \ --header 'Content-Type: application/json' \ --header "x-agent-id: $AGENT_ID" \ --header "x-agent-api-key: $AGENT_KEY" \ --data '{ "externalId": "funding-001", "asset": "USD", "nickname": "My Debit Card", "paymentMethod": { "type": "CARD", "token": "tok_sandbox_test_token", "billingAddress": { "countryCode": "US", "stateCode": "CA", "city": "San Francisco", "line1": "123 Market St", "zipcode": "94105" } } }' ``` > ⚠️ **Save the returned `id`** β€” this is the `fundingAccountId`. #### Step 8: Execute the Transaction Link all the IDs together and submit: ```bash curl --request POST \ --url https://api.sandbox.inyoplatform.com/organizations/$TENANT/fx/transactions \ --header 'Content-Type: application/json' \ --header "x-agent-id: $AGENT_ID" \ --header "x-agent-api-key: $AGENT_KEY" \ --data '{ "senderId": "'$SENDER_ID'", "recipientId": "'$RECIPIENT_ID'", "fundingAccountId": "'$FUNDING_ACCOUNT_ID'", "recipientAccountId": "'$RECIPIENT_ACCOUNT_ID'", "quoteId": "'$QUOTE_ID'", "additionalData": {}, "deviceData": { "userIpAddress": "127.0.0.1" } }' ``` A successful response returns HTTP `202` with the full transaction object, including `complianceStatus`, `payoutStatus`, exchange rate details, and the `receipt` object containing legally required disclosures. *** ### What's Next? * [Set up webhooks](webhooks.md) to receive real-time transaction status updates * Review the [transaction lifecycle](transaction.md) to understand status transitions * Explore [compliance levels](sender/trust-level-limits.md) to build progressive KYC flows * Check [test data](sender/test-data.md) for sandbox testing scenarios *** ### Common Issues | Symptom | Cause | Fix | | ----------------------- | ------------------------------------------------------ | ---------------------------------------------------------------------- | | `401 Unauthorized` | Invalid API keys | Verify `x-api-key`, `x-agent-id`, and `x-agent-api-key` in your `.env` | | `403 Forbidden` | Agent not approved, or sender below compliance Level 1 | Check agent approval status; verify sender's compliance level | | `400 Missing fields` | Incomplete sender/recipient profile | Check schema endpoints for required fields per country | | `422 Unprocessable` | Quote expired, limit exceeded, or invalid references | Refresh the quote; check limits via `GET /fx/participants/{id}/limits` | | `429 Too Many Requests` | Rate limit exceeded | Implement caching for destinations, banks, and schemas (24h TTL) | *** ### Feedback & Support We're continuously improving these docs. For feedback, questions, or technical issues: * πŸ“§ Get in touch with our sales team * πŸ“– [Interactive API reference](https://dev-api.inyoglobal.com/sandbox/) * πŸ’» [Sample project on GitHub](https://github.com/inyo-global/remittance-sample) # Authentication Inyo uses API keys to securely control access to its APIs. All requests must be authenticated β€” unauthenticated requests are rejected by the firewall. *** ### Credentials During onboarding, Inyo provides you with the following credentials: | Credential | Header | Description | | ---------- | ------ | ----------- | | Tenant ID | Path parameter (`{tenant}`) | Your organization identifier. Used in all endpoint URLs. | | Tenant API Key | `x-api-key` | Primary key for account management, compliance queries, and listing operations. | | Agent ID | `x-agent-id` | UUID identifying the agent that executes transactional operations. | | Agent API Key | `x-agent-api-key` | Secret key paired with the agent, used for quotes, transactions, and payouts. | *** ### Authentication Layers The API uses a **dual-layer authentication model**. Different endpoints require different credential combinations: #### Tenant-Level Authentication Used for account management, compliance, and read operations. ```http GET /organizations/{tenant}/people/{personId} Content-Type: application/json Accept: application/json x-api-key: {your_tenant_api_key} ``` **Endpoints using Tenant auth:** * Person/Company CRUD (`POST /people`, `PATCH /people/{id}`, etc.) * Compliance levels (`GET /participants/{id}/complianceLevels`) * Transaction listing and status (`GET /fx/transactions`, `GET /fx/transactions/{id}/status`) * Document uploads (`POST /people/{id}/documents/...`) * Webhook management (`POST /webhooks`, `GET /webhooks`) * Transaction cancel/refund operations * Address verification #### Agent-Level Authentication Used for transactional operations that move money. Requires **both** the Tenant API key and Agent credentials. ```http POST /organizations/{tenant}/fx/transactions Content-Type: application/json Accept: application/json x-api-key: {your_tenant_api_key} x-agent-id: {your_agent_id} x-agent-api-key: {your_agent_api_key} ``` **Endpoints using Agent auth:** * Quotes (`POST /payout/quotes`) * Transactions (`POST /fx/transactions`) * Funding accounts (`POST /payout/participants/{id}/fundingAccounts`) * Recipient accounts (`POST /payout/participants/{id}/recipientAccounts/gateway`) * Wallet operations > ⚠️ **Agent Approval Required**: Newly created agents must be approved by the Inyo compliance team before they can execute transactions. Before approval, agent-authenticated requests will return `403 Forbidden`. *** ### Required Headers Include the following headers on **every** request: ```http Content-Type: application/json Accept: application/json x-api-key: {your_tenant_api_key} ``` For agent-authenticated endpoints, also include: ```http x-agent-id: {your_agent_id} x-agent-api-key: {your_agent_api_key} ``` *** ### Environments | Environment | Base URL | | ----------- | -------- | | Sandbox | `https://api.sandbox.inyoplatform.com` | | Production | Provided during go-live (contact your Inyo account manager) | > πŸ’‘ **Sandbox and production use separate credentials.** Never use sandbox keys in production or vice versa. *** ### Security Best Practices * **Never expose API keys in frontend code.** Use a Backend-for-Frontend (BFF) pattern β€” your server holds the secrets and proxies requests to Inyo. * **Rotate keys** if you suspect compromise. Contact our sales team for key rotation. * **Restrict network access** β€” use IP allowlisting in production if supported by your plan. * **Use HTTPS only** β€” all Inyo endpoints enforce TLS. Plain HTTP requests are rejected. *** ### Error Responses | HTTP Status | Meaning | | ----------- | ------- | | `401 Unauthorized` | Missing or invalid `x-api-key`. | | `403 Forbidden` | Valid key but insufficient permissions β€” agent not approved, or endpoint requires agent auth. | | `429 Too Many Requests` | Rate limit exceeded. Implement caching and backoff. | # Data Population Before building transaction flows, you need to populate your application with reference data from the Inyo API. This data includes available payout countries, required schemas for recipients and accounts, and bank lists. *** ### Overview | Endpoint | Purpose | Cache Duration | | -------- | ------- | -------------- | | [Payout Countries](payout-countries-enabled.md) | Available destination countries for a source corridor | 24 hours | | [Recipient Schema](recipient-schema.md) | Required fields to create a recipient in a given country | 24 hours | | [Recipient Account Schema](recipient-account-schema.md) | Required fields to link a bank account in a given country | 24 hours | | [Transaction Schema](transaction-schema.md) | Country-specific `additionalData` fields for transactions | 24 hours | | [Banks in a Country](banks-in-a-country.md) | List of banks available for a destination country | 24 hours | *** ### Rate Limits All data population endpoints are subject to strict rate limits. These endpoints serve reference data that changes infrequently β€” you are expected to **cache responses locally** and refresh periodically (recommended: every 24 hours). **Do not call these endpoints on every transaction.** Excessive calls will result in `429 Too Many Requests` responses. *** ### Recommended Caching Strategy ```javascript // Example: Cache with 24-hour TTL const NodeCache = require('node-cache'); const cache = new NodeCache({ stdTTL: 86400 }); // 24 hours async function getDestinations(sourceCountry) { const cacheKey = `destinations:${sourceCountry}`; let data = cache.get(cacheKey); if (!data) { data = await inyoApi.get(`/payout/${sourceCountry}/destinations`); cache.set(cacheKey, data); } return data; } ``` *** ### Schema-Driven Integration A key design principle of the Inyo API is that **data requirements vary by country**. Instead of hardcoding field requirements, your integration should: 1. **Fetch the schema** for the destination country 2. **Dynamically render forms** based on required/optional fields 3. **Validate user input** against the schema before submitting This approach ensures your application automatically adapts when Inyo adds new countries or changes requirements β€” without code changes on your side. ``` User selects destination country β”‚ β–Ό Fetch recipient schema ──────────▢ Build person form Fetch account schema ────────────▢ Build bank account form Fetch bank list (if needed) ─────▢ Populate bank dropdown Fetch transaction schema ────────▢ Collect additionalData fields ``` # Payout countries enabled The Payout Destinations endpoint returns the list of countries available to receive payouts from a specified source country. This is typically used to determine which destination countries are supported for a payout corridor (that are active for your installation) before proceeding with quote or transaction creation. | Parameter | Type | Required | Description | | ----------------- | ------ | -------- | ----------------------------------------------------------------- | | tenant | string | Yes | The tenant identifier for your organization. | | sourceCountryIso2 | string | Yes | ISO 3166-1 alpha-2 code of the source country (e.g., US, BR, MX). | ### Sample Request {% tabs %} {% tab title="Request" %} ``` curl --request GET \ --url {api_endpoint}/organizations/{tenant}/payout/{sourceCountryIso2}/destinations \ --header 'content-type: application/json' \ --header 'x-agent-api-key: {agentApiKey}' \ --header 'x-agent-id: {agentId}' ``` {% endtab %} {% tab title="Response" %} ``` { "countryDestinations": [ { "country": "BR", "countryName": "Brazil", "currency": "BRL", "currencyName": "Brazilian real", "currencySymbol": "R$", "emoji": "" }, { "country": "DO", "countryName": "Dominican Republic", "currency": "DOP", "currencyName": "Dominican peso", "currencySymbol": "RD$", "emoji": "" } ] } ``` {% endtab %} {% endtabs %} ### Response Details * country β€” ISO 3166-1 alpha-2 code of the destination country. * countryName β€” Full name of the destination country. * currency β€” ISO 4217 code of the destination currency. * currencyName β€” Full name of the currency. * currencySymbol β€” Symbol for the destination currency (if available). * emoji β€” (Optional) Flag emoji representation. *** ### Usage Notes * Use this endpoint to populate dropdown lists or validate corridors when the user selects a source country. * Once a valid source–destination pair is chosen, you can proceed to request quotes and create payouts. [Documentation](https://dev-api.inyoglobal.com/sandbox/#tag/payout_Payout-Resource/paths/~1organizations~1%7Btenant%7D~1payout~1%7BsourceCountryIso2%7D~1destinations/get) ``` GET /organizations/{tenant}/payout/{sourceCountryIso2}/destinations ``` # Recipient schema Each country requires a set of different information for the recipient, and that schema is described by | Parameter | Type | Required | Description | | ---------------------- | ------ | -------- | ---------------------------------------------------------------------- | | tenant | string | Yes | The tenant identifier for your organization. | | destinationCountryIso2 | string | Yes | ISO 3166-1 alpha-2 code of the destination country (e.g., US, BR, MX). | ### Sample Request {% tabs %} {% tab title="Request" %} ``` curl --request GET \ --url {api_endpoint}/organizations/{tenant}/payout/recipients/schema/{destinationCountryIso2} \ --header 'content-type: application/json' \ --header 'x-agent-api-key: {agentApiKey}' \ --header 'x-agent-id: {agentId}' ``` {% endtab %} {% tab title="Response" %} ``` { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Inyo Global Recipient Data - Colombia", "description": "Schema for a COL person.", "version": "1.0.0", "type": "object", "required": [ "firstName", "lastName", "address", "documents" ], "properties": { "firstName": { "type": "string", "description": "The first name of the person.", "minLength": 1 }, "lastName": { "type": "string", "description": "The last name of the person.", "minLength": 1 }, "birthDate": { "type": "string", "description": "The birth date of the person. Formatted as yyyy-MM-dd", "pattern": "^\\d{4}-\\d{2}-\\d{2}$" }, "phoneNumber": { "type": "string", "description": "The contact phone number of the person.", "minLength": 1 }, "documents": { "type": "array", "description": "A list of identification documents for the person.", "minItems": 1, "items": { "type": "object", "required": [ "document", "type" ], "properties": { "document": { "type": "string", "description": "The document number." }, "type": { "type": "string", "description": "The type of document.", "enum": [ "CC" ] }, "countryCode": { "type": "string", "description": "The country code of document.", "enum": [ "CO" ] } }, "if": { "properties": { "type": { "const": "CC" } } }, "then": { "properties": { "document": { "description": "CΓ©dula de Ciudadania", "pattern": "^(\\d){8,10}" } } } } } } } ``` {% endtab %} {% endtabs %} *** [Documentation](https://dev-api.inyoglobal.com/sandbox/#tag/payout_Payout-Resource/paths/~1organizations~1%7Btenant%7D~1payout~1recipients~1schema~1%7BcountryIso2%7D/get) ``` GET /organizations/{tenant}/payout/recipients/schema/{destinationCountryIso2} ``` # Recipient account schema For a specific payout method, the schema can vary and the amount of information too. | Parameter | Type | Required | Description | | ---------------------- | ------ | -------- | ---------------------------------------------------------------------- | | tenant | string | Yes | The tenant identifier for your organization. | | destinationCountryIso2 | string | Yes | ISO 3166-1 alpha-2 code of the destination country (e.g., US, BR, MX). | ### Sample Request {% tabs %} {% tab title="Request" %} ``` curl --request GET \ --url {api_endpoint}/organizations/{tenant}/payout/recipientAccounts/schema/{destinationCountryIso2} \ --header 'content-type: application/json' \ --header 'x-agent-api-key: {agentApiKey}' \ --header 'x-agent-id: {agentId}' ``` {% endtab %} {% tab title="Response" %} ``` { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Inyo Global Recipient Account Data - Colombia", "description": "Schema for a COL account.", "version": "1.0.0", "type": "object", "required": [ "asset", "payoutMethod" ], "properties": { "externalId": { "type": "string", "description": "An unique identifier from your system." }, "asset": { "type": "string", "description": "The currency code that identifies the recipient account's currency in the 'ISO 4217' format.", "enum": [ "COP" ] }, "payoutMethod": { "type": "object", "description": "Details of the payout method.", "required": [ "countryCode", "bankCode", "routingNumber", "accountNumber", "accountType" ], "properties": { "type": { "type": "string", "description": "The type of payout method.", "enum": [ "BANK_DEPOSIT" ] }, "countryCode": { "type": "string", "description": "The country code of the bank account.", "enum": [ "CO" ] }, "bankCode": { "type": "string", "description": "The code of the bank.", "minLength": 1 }, "routingNumber": { "type": "string", "description": "The routing number for the bank (e.g., ABA for USA, SWIFT/BIC for international).", "minLength": 3 }, "accountNumber": { "type": "string", "description": "The bank account number.", "minLength": 1 }, "accountType": { "type": "string", "description": "The type of bank account.", "enum": [ "CHECKING", "SAVINGS" ] } } } } } ``` {% endtab %} {% endtabs %} *** [Documentation](https://dev-api.inyoglobal.com/sandbox/#tag/payout_Payout-Resource/paths/~1organizations~1%7Btenant%7D~1payout~1recipientAccounts~1schema~1%7BcountryIso2%7D/get) ``` GET /organizations/{tenant}/payout/recipientAccounts/schema/{destinationCountryIso2} ``` # Transaction schema Each country destination requires a set of different information for the additional data in transaction, and that schema is described by | Parameter | Type | Required | Description | | ---------------------- | ------ | -------- | ---------------------------------------------------------------------- | | tenant | string | Yes | The tenant identifier for your organization. | | destinationCountryIso2 | string | Yes | ISO 3166-1 alpha-2 code of the destination country (e.g., US, BR, MX). | ### Sample Request {% tabs %} {% tab title="Request" %} ``` curl --request GET \ --url '{api_endpoint}/organizations/{tenant}/fx/transactions/schema?countryCode={destinationCountryIso2}' \ --header 'content-type: application/json' \ --header 'x-agent-api-key: {agentApiKey}' \ --header 'x-agent-id: {agentId}' ``` {% endtab %} {% tab title="Response" %} ``` { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Inyo Global Payout Additional Data - Morocco", "version": "1.0.0", "type": "object", "properties": { "additionalData": { "type": "object", "properties": { "statementNarrative": { "type": "string", "description": "Optional narrative text providing transaction details." } } } }, "required": [ "additionalData" ] } ``` {% endtab %} {% endtabs %} [Documentation](https://dev-api.inyoglobal.com/sandbox/#tag/fx_Transaction-Resource/paths/~1organizations~1%7Btenant%7D~1fx~1transactions~1schema/get) ``` GET /organizations/{tenant}/fx/transactions/schema?countryCode={destinationCountryIso2} ``` # Banks in a country For some country destinations we have a list of the available banks. | Parameter | Type | Required | Description | | ---------------------- | ------ | -------- | ---------------------------------------------------------------------- | | tenant | string | Yes | The tenant identifier for your organization. | | destinationCountryIso2 | string | Yes | ISO 3166-1 alpha-2 code of the destination country (e.g., US, BR, MX). | ### Sample Request {% tabs %} {% tab title="Request" %} ``` curl --request GET \ --url {api_endpoint}/organizations/{tenant}/payout/{destinationCountryIso2}/banks \ --header 'content-type: application/json' \ --header 'x-agent-api-key: {agentApiKey}' \ --header 'x-agent-id: {agentId}' ``` {% endtab %} {% tab title="Response" %} ```json { "total": 47, "size": 10, "page": 0, "numberOfPages": 5, "items": [ { "code": "007", "name": "BANCO BCSC", "countryIso2": "CO" }, { "code": "1000", "name": "BANCO REPÚBLICA", "countryIso2": "CO" }, { "code": "1001", "name": "BANCO DE BOGOTÁ", "countryIso2": "CO" } ... ] } ``` {% endtab %} {% endtabs %} [Documentation](https://dev-api.inyoglobal.com/sandbox/#tag/payout_Payout-Resource/paths/~1organizations~1%7Btenant%7D~1payout~1%7BcountryIso2%7D~1banks/get) ``` GET /organizations/{tenant}/payout/{destinationCountryIso2}/banks ``` # Quotes (FX) The FX Quote API allows you to request and lock in foreign exchange rates for a specific currency corridor and amount. The returned `quoteId` guarantees the rate for a fixed window, protecting both you and your customer from FX fluctuations during the transaction flow. *** ### How Pricing Works Two components determine the cost to the end user: * **FX Spread** β€” Your margin on the exchange rate, configured per corridor by Inyo during onboarding. * **Transaction Fee** β€” A per-transaction fee, also configured during onboarding. Depending on your contract, you may be able to override fees at the quote level (e.g., for promotional "zero fee" offers). #### Amount Types When requesting a quote, the `amountType` field controls how fees are applied: | Type | Behavior | Example (fee = $4, amount = $100) | | ---- | -------- | --------------------------------- | | `NET` | Fee is added on top of the amount. The full amount is converted. | Sender pays **$104**. $100 is converted to destination currency. | | `GROSS` | Fee is deducted from the amount. Only the remainder is converted. | Sender pays **$100**. $96 is converted to destination currency. | *** ### Requesting a Quote **Endpoint:** `POST /organizations/{tenant}/payout/quotes`\ **Authentication:** Agent-level (`x-api-key` + `x-agent-id` + `x-agent-api-key`) #### Request Body | Field | Type | Required | Description | | ----- | ---- | -------- | ----------- | | `fromCurrency` | string | Yes | Source currency (ISO 4217), e.g., `USD` | | `toCurrency` | string | Yes | Destination currency (ISO 4217), e.g., `BRL` | | `amount` | number | Yes | Amount to convert | | `fee` | object | No | Override the default fee (if allowed by your contract) | | `fee.amount` | number | β€” | Fee amount | | `fee.currency` | string | β€” | Fee currency (typically matches `fromCurrency`) | | `amountType` | string | No | `GROSS` or `NET` (see above) | #### Sample Request ```bash curl --request POST \ --url https://api.sandbox.inyoplatform.com/organizations/$TENANT/payout/quotes \ --header 'Content-Type: application/json' \ --header "x-api-key: $API_KEY" \ --header "x-agent-id: $AGENT_ID" \ --header "x-agent-api-key: $AGENT_KEY" \ --data '{ "fromCurrency": "USD", "toCurrency": "BRL", "amount": 1000, "fee": { "amount": 4.99, "currency": "USD" }, "amountType": "NET" }' ``` #### Sample Response ```json { "quotes": [ { "id": "f28be3f9-ad4d-4a6c-a85d-ae5515a4a158", "agentId": "8500786a-68e9-45fd-b1d8-c590f1e06450", "fromAsset": "USD", "toAsset": "BRL", "effectiveRate": "5.39023000", "totalCost": { "amount": "5.23", "currency": "USD" }, "product": "ACCOUNT", "expireAt": "2025-09-15T11:39:01Z", "createdAt": "2025-09-15T11:37:01Z", "sourceAmount": { "amount": "1000.00", "currency": "USD" }, "destinationAmount": { "amount": "5390.23", "currency": "BRL" } } ] } ``` *** ### Response Fields | Field | Description | | ----- | ----------- | | `id` | Unique quote identifier. **Save this** β€” it's required for the transaction. | | `fromAsset` / `toAsset` | Source and destination currencies | | `effectiveRate` | The FX rate applied for this conversion (includes your spread) | | `sourceAmount` | The amount in the source currency | | `destinationAmount` | The amount the recipient will receive | | `totalCost` | Total fees/costs in the source currency | | `product` | Quote context: `ACCOUNT` (bank deposit), `WALLET`, or `CARD` | | `expireAt` | ISO 8601 timestamp when the quote becomes invalid | | `createdAt` | ISO 8601 timestamp when the quote was generated | *** ### Example Conversion Walkthrough **Scenario:** Convert 1,000 USD β†’ BRL | Field | Value | | ----- | ----- | | Source Currency | USD | | Destination Currency | BRL | | Source Amount | 1,000.00 USD | | Effective Rate | 5.39023000 | | Destination Amount | 5,390.23 BRL | | Total Cost (fees) | 5.23 USD | | Product | ACCOUNT | | Quote Validity | ~2 minutes | *** ### Retrieving a Quote **Endpoint:** `GET /organizations/{tenant}/payout/quotes/{quoteId}`\ **Authentication:** Agent-level ```bash curl --request GET \ --url https://api.sandbox.inyoplatform.com/organizations/$TENANT/payout/quotes/$QUOTE_ID \ --header "x-api-key: $API_KEY" \ --header "x-agent-id: $AGENT_ID" \ --header "x-agent-api-key: $AGENT_KEY" ``` Use this to check if a quote is still valid before submitting a transaction. *** ### Handling Quote Expiration Quotes have a short validity window (typically ~2 minutes). Follow these guidelines: * **Always check `expireAt`** before using a quote in a transaction. * **Refresh proactively** β€” request a new quote when ~90% of the validity period has elapsed. * **Never cache quotes** β€” unlike destinations and bank lists, quotes are time-sensitive and must be fetched fresh. * **Expired quotes will be rejected** β€” if you submit a transaction with an expired `quoteId`, the API returns a `400` or `422` error. #### Recommended UX Pattern ``` 1. User selects amount and destination β†’ Request quote 2. Display rate, fees, and receive amount 3. Start a countdown timer based on expireAt 4. If timer reaches ~80%, show "Rate expiring" warning 5. If expired, auto-refresh and update the display 6. On "Confirm", immediately submit the transaction with the quoteId ``` *** ### All Endpoints | Operation | Method | Endpoint | | --------- | ------ | -------- | | Request quote | `POST` | `/organizations/{tenant}/payout/quotes` | | Get quote by ID | `GET` | `/organizations/{tenant}/payout/quotes/{quoteId}` | [Interactive API Documentation](https://dev-api.inyoglobal.com/sandbox/#tag/payout_Quote-Resource) # Sender The Sender is the participant who initiates and pays for a transaction. Every sender must pass through KYC (Know Your Customer) screening, and their transaction limits are determined by their compliance level. *** ### Initial Compliance Setup At the beginning of your integration, the Inyo compliance team works with your organization to define a custom compliance framework tailored to your product and customer profile. This framework determines: * **Compliance levels** β€” tiers that define how much a customer can send within set timeframes (24h, 30d, 180d) * **Validation rules** β€” the required data and documents for each level (e.g., name, SSN, proof of income) * **Risk controls** β€” thresholds that trigger enhanced due diligence This configuration is unique per tenant and directly impacts how participants are verified and which operations they can perform. *** ### Creating a Sender **Endpoint:** `POST /organizations/{tenant}/people`\ **Authentication:** Tenant-level (`x-api-key`) No fields are technically *required* to create a person β€” but to use them as a sender, they must reach at least **Compliance Level 1**, which typically requires first name, last name, address, and phone number. ```bash curl --request POST \ --url https://api.sandbox.inyoplatform.com/organizations/$TENANT/people \ --header 'Content-Type: application/json' \ --header "x-api-key: $API_KEY" \ --data '{ "firstName": "John", "lastName": "Doe", "email": "john.doe@example.com", "birthDate": "1990-01-15", "phoneNumber": "+15551234567", "gender": "Male", "externalId": "your-internal-id-001", "address": { "countryCode": "US", "stateCode": "CA", "city": "San Francisco", "line1": "123 Market St", "zipcode": "94105" }, "documents": [ { "type": "SSN", "document": "123456789", "countryCode": "US" } ], "occupation": "Software Engineer" }' ``` #### Request Fields | Field | Type | Required | Description | | ----- | ---- | -------- | ----------- | | `firstName` | string | For Level 1 | First name | | `lastName` | string | For Level 1 | Last name | | `phoneNumber` | string | For Level 1 | Phone with country code (e.g., `+15551234567`) | | `email` | string | For US persons | Email address | | `gender` | string | For US persons | `Male`, `Female`, or `Other` | | `birthDate` | string | For US persons | Format: `yyyy-MM-dd` | | `externalId` | string | No | Your internal reference ID | | `address` | object | For Level 1 | Residential address | | `address.countryCode` | string | Yes (in address) | ISO 3166-1 alpha-2 | | `address.stateCode` | string | For US | US state code (e.g., `CA`) | | `address.city` | string | Yes (in address) | City name | | `address.line1` | string | Yes (in address) | Street address | | `address.line2` | string | No | Additional address info | | `address.zipcode` | string | Yes (in address) | Postal code | | `documents` | array | For Level 2+ | Identity documents | | `documents[].type` | string | Yes (in doc) | `SSN`, `ITIN` (US), `CPF` (BR), etc. | | `documents[].document` | string | Yes (in doc) | Document number | | `documents[].countryCode` | string | Yes (in doc) | Issuing country | | `occupation` | string | For Level 2 | Person's occupation | | `employerName` | string | No | Employer name | #### Sample Response ```json { "id": "48066496-9445-41b7-acbe-85e069a77cb7", "firstName": "John", "lastName": "Doe", "mainAddressId": "9c2ea7e5-51a2-4ea1-83cf-8948754486f8", "phoneNumber": "+15551234567", "email": "john.doe@example.com", "gender": "Male", "birthDate": "1990-01-15", "externalId": "your-internal-id-001", "updatedAt": "2025-01-15T12:00:00", "documents": [], "occupation": "Software Engineer", "documentId": null, "sourceOfFundsId": null, "employerName": null, "employerAddressId": null } ``` > ⚠️ **Save the returned `id`** β€” this is the `senderId` used in all subsequent API calls. *** ### Updating a Sender **Endpoint:** `PATCH /organizations/{tenant}/people/{personId}`\ **Authentication:** Tenant-level Only fields included in the request body are updated; omitted fields remain unchanged. ```bash curl --request PATCH \ --url https://api.sandbox.inyoplatform.com/organizations/$TENANT/people/$PERSON_ID \ --header 'Content-Type: application/json' \ --header "x-api-key: $API_KEY" \ --data '{ "occupation": "Consultant", "documents": [ { "type": "SSN", "document": "987654321", "countryCode": "US" } ] }' ``` *** ### Address Verification US addresses are automatically validated through USPS APIs. If validation fails, the sender's transactions will be placed **on hold** until the compliance team manually verifies the address. **Verify an address before submitting:** ```bash curl --request GET \ --url "https://api.sandbox.inyoplatform.com/organizations/$TENANT/addresses/check?countryCode=US&stateCode=CA&city=San+Francisco&line1=123+Market+St&line2=&zipCode=94105" \ --header "x-api-key: $API_KEY" ``` | Response Code | Meaning | | ------------- | ------- | | `202` | Address is valid | | `200` | Address suggestion returned (USPS found a corrected version) | | `400` | Address validation failed | *** ### Creating Business Senders (KYB) For B2B use cases, you can create a company as a sender: **Endpoint:** `POST /organizations/{tenant}/companies`\ **Authentication:** Tenant-level Companies follow a similar compliance level system but with different required fields (business registration, EIN, etc.). Contact your Inyo account manager for your tenant-specific KYB configuration. *** ### Compliance Levels | Level | Typical Required Fields | Description | | ----- | ----------------------- | ----------- | | Level 0 | (none) | Cannot transact | | Level 1 | firstName, lastName, address, phoneNumber | Basic KYC | | Level 2 | SSN, occupation, document ID | Enhanced KYC | | Level 3 | Proof of source of funds | Full KYC | > ⚠️ These are customizable per tenant. See [Trust Level Limits](trust-level-limits.md) for details. *** ### Best Practices * **Progressive onboarding** β€” Collect only Level 1 fields at signup. Prompt for more data when the user needs higher limits. * **Pre-validate addresses** β€” Use the address check endpoint before creating the sender to avoid holds. * **Sync compliance state** β€” After profile updates, re-check the compliance level to see if it has upgraded. * **Handle restricted users** β€” If a sender is `Restricted`, check `GET /participants/{id}/complianceLevels` to understand why and what action is needed. *** ### Related Pages * [Trust Level Limits](trust-level-limits.md) β€” Check and upgrade compliance levels * [Uploading Documents](uploading-documents.md) β€” Submit ID and source-of-funds documents * [Test Data](test-data.md) β€” Sandbox testing scenarios for compliance flows ### All Endpoints | Operation | Method | Endpoint | | --------- | ------ | -------- | | Create person | `POST` | `/organizations/{tenant}/people` | | Update person | `PATCH` | `/organizations/{tenant}/people/{personId}` | | Get person | `GET` | `/organizations/{tenant}/people/{personId}` | | Get person with details | `GET` | `/organizations/{tenant}/people/{personId}/details` | | Update address | `PUT` | `/organizations/{tenant}/people/{personId}/address` | | Update employer address | `PUT` | `/organizations/{tenant}/people/{personId}/employerAddress` | | Update place of birth | `PUT` | `/organizations/{tenant}/people/{personId}/placeOfBirth` | | Check address | `GET` | `/organizations/{tenant}/addresses/check` | [Interactive API Documentation](https://dev-api.inyoglobal.com/sandbox/#tag/account_Person-Resource) # Trust Level Limits The Trust Level (Compliance Level) system controls how much a sender is allowed to transact. Higher levels require more KYC data and documents but unlock higher transaction limits across three rolling time windows: **24 hours**, **30 days**, and **180 days**. *** ### Checking Compliance Level **Endpoint:** `GET /organizations/{tenant}/participants/{participantId}/complianceLevels`\ **Authentication:** Tenant-level (`x-api-key`) ```bash curl --request GET \ --url https://api.sandbox.inyoplatform.com/organizations/$TENANT/participants/$SENDER_ID/complianceLevels \ --header "x-api-key: $API_KEY" \ --header "x-agent-id: $AGENT_ID" \ --header "x-agent-api-key: $AGENT_KEY" ``` This endpoint returns: * The participant's **current compliance level** and its limits * The **next available level** and the fields required to upgrade *** ### Sample Response A participant at `LEVEL_0` (cannot transact yet) with upgrade path to `LEVEL_1`: ```json { "currentLevel": { "level": "LEVEL_0", "limits": { "oneDayLimit": { "amount": "0.00", "currency": "USD" }, "thirtyDaysLimit": { "amount": "0.00", "currency": "USD" }, "oneHundredAndEightyDaysLimit": { "amount": "0.00", "currency": "USD" } } }, "nextLevel": { "level": "LEVEL_1", "limits": { "oneDayLimit": { "amount": "2999.00", "currency": "USD" }, "thirtyDaysLimit": { "amount": "6000.00", "currency": "USD" }, "oneHundredAndEightyDaysLimit": { "amount": "9999.00", "currency": "USD" } }, "requiredFields": [ "firstName", "lastName", "phoneNumber", "residentialAddress" ] } } ``` *** ### Compliance Level Overview | Level | Typical Required Fields | Description | | ----- | ----------------------- | ----------- | | `LEVEL_0` | (none) | Default level. **Cannot transact.** Participant exists but has not provided minimum KYC data. | | `LEVEL_1` | firstName, lastName, address, phoneNumber | Basic KYC. Enables standard transaction volumes. | | `LEVEL_2` | SSN/ITIN, occupation, document ID upload | Enhanced KYC. Unlocks higher limits. | | `LEVEL_3` | Proof of source of funds | Full KYC. Maximum transaction limits. | > ⚠️ **These levels are customizable per tenant.** Your specific required fields and limits are defined during the Inyo onboarding process and may differ from the defaults shown above. *** ### Checking Transaction Limits & Usage To see how much of their limit a sender has already used: **Endpoint:** `GET /organizations/{tenant}/fx/participants/{participantId}/limits`\ **Authentication:** Agent-level (`x-api-key` + `x-agent-id` + `x-agent-api-key`) ```bash curl --request GET \ --url https://api.sandbox.inyoplatform.com/organizations/$TENANT/fx/participants/$SENDER_ID/limits \ --header "x-api-key: $API_KEY" \ --header "x-agent-id: $AGENT_ID" \ --header "x-agent-api-key: $AGENT_KEY" ``` **Sample Response:** ```json { "oneDayLimit": { "limit": { "amount": "2999.00", "currency": "USD" }, "used": { "amount": "500.00", "currency": "USD" }, "available": { "amount": "2499.00", "currency": "USD" } }, "thirtyDaysLimit": { "limit": { "amount": "6000.00", "currency": "USD" }, "used": { "amount": "2959.99", "currency": "USD" }, "available": { "amount": "3040.01", "currency": "USD" } }, "oneHundredAndEightyDaysLimit": { "limit": { "amount": "9999.00", "currency": "USD" }, "used": { "amount": "5000.00", "currency": "USD" }, "available": { "amount": "4999.00", "currency": "USD" } }, "complianceLevel": { "level": "LEVEL_1", "requiredFields": ["birthDate", "lastName", "name", "occupation", "phoneNumber", "residentialAddress"], "perTransactionFields": [] } } ``` **Key fields:** * `limit` β€” Maximum amount for this time window * `used` β€” Amount already consumed in the rolling window * `available` β€” Remaining capacity (`limit - used`) * `complianceLevel.requiredFields` β€” Fields needed for the *next* level upgrade *** ### Upgrading a Sender's Level To move a sender from one level to the next: 1. **Check current level** β€” `GET /participants/{id}/complianceLevels` 2. **Identify missing fields** β€” Look at `nextLevel.requiredFields` 3. **Collect the data** β€” Update the sender's profile with the missing fields: * Use `PATCH /people/{personId}` for profile fields (SSN, occupation, etc.) * Use `PUT /people/{personId}/address` for address updates * Use document upload endpoints for ID documents and source of funds 4. **Verify the upgrade** β€” Call the compliance levels endpoint again to confirm the level increased ``` LEVEL_0 LEVEL_1 LEVEL_2 LEVEL_3 β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Cannot β”‚ Add β”‚ Standard β”‚ Add β”‚ Enhanced β”‚ Add β”‚ Maximum β”‚ β”‚ transact │──────▢│ limits │──────▢│ limits │──────▢│ limits β”‚ β”‚ β”‚ name, β”‚ β”‚ SSN, β”‚ β”‚ proof β”‚ β”‚ β”‚ $0 / $0 / $0β”‚ addr, β”‚ $X/$X/$X β”‚ docs, β”‚ $X/$X/$X β”‚ of β”‚ $X/$X/$X β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ phone β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ occup β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ funds β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ``` *** ### Use Cases * **Progressive onboarding** β€” Start with minimal data (Level 1) and prompt for more only when the user needs higher limits. * **Limit warnings** β€” Show users their remaining capacity before they initiate a transaction. * **Upgrade prompts** β€” When a transaction would exceed the current limit, show exactly which fields are missing and guide the user to complete them. * **Compliance dashboard** β€” Display a visual progress bar showing the user's level and what's needed for the next tier. *** ### Pending Verifications To check what the compliance team is still reviewing for a participant: **Endpoint:** `GET /organizations/{tenant}/participants/{participantId}/pendingVerifications`\ **Authentication:** Tenant-level ```bash curl --request GET \ --url https://api.sandbox.inyoplatform.com/organizations/$TENANT/participants/$SENDER_ID/pendingVerifications \ --header "x-api-key: $API_KEY" ``` This is useful for showing users that their documents are under review and their level upgrade is in progress. [Interactive API Documentation](https://dev-api.inyoglobal.com/sandbox/#tag/account_Participant-Resource) # Uploading Documents To advance a sender to higher compliance levels (Level 2 and above), you may need to upload identity documents and proof of source of funds. These documents are reviewed by the Inyo compliance team. *** ### Document Types #### Identity Documents (Document ID) Government-issued identification uploaded for KYC verification. **Endpoint:** `POST /organizations/{tenant}/people/{personId}/documents/documentId/{subtype}/upload`\ **Authentication:** Tenant-level (`x-api-key`) **Supported Subtypes:** | Subtype | Description | | ------- | ----------- | | `FRONT` | Front side of the document | | `BACK` | Back side of the document | **Accepted document types** (configured per tenant): * Driver's License * Passport * National ID Card * State ID #### Source of Funds Proof of the sender's source of funds, required for higher compliance tiers. **Endpoint:** `POST /organizations/{tenant}/people/{personId}/documents/sourceOfFunds/{subtype}/upload`\ **Authentication:** Tenant-level (`x-api-key`) **Supported Subtypes:** | Subtype | Description | | ------- | ----------- | | `FRONT` | Primary document (e.g., bank statement, pay stub) | *** ### Upload Request Documents are uploaded as **multipart/form-data** with the file attached. ```bash # Upload front of driver's license curl --request POST \ --url https://api.sandbox.inyoplatform.com/organizations/$TENANT/people/$PERSON_ID/documents/documentId/FRONT/upload \ --header "x-api-key: $API_KEY" \ --form "file=@/path/to/drivers-license-front.jpg" ``` ```bash # Upload back of driver's license curl --request POST \ --url https://api.sandbox.inyoplatform.com/organizations/$TENANT/people/$PERSON_ID/documents/documentId/BACK/upload \ --header "x-api-key: $API_KEY" \ --form "file=@/path/to/drivers-license-back.jpg" ``` ```bash # Upload source of funds curl --request POST \ --url https://api.sandbox.inyoplatform.com/organizations/$TENANT/people/$PERSON_ID/documents/sourceOfFunds/FRONT/upload \ --header "x-api-key: $API_KEY" \ --form "file=@/path/to/bank-statement.pdf" ``` *** ### Document Verification Status After uploading, documents are queued for review by the compliance team. You can track the verification status: #### Get Current Verification Status **Endpoint:** `GET /organizations/{tenant}/documents/{documentId}/status`\ **Authentication:** Tenant-level ```bash curl --request GET \ --url https://api.sandbox.inyoplatform.com/organizations/$TENANT/documents/$DOCUMENT_ID/status \ --header "x-api-key: $API_KEY" ``` #### Get Verification Status History **Endpoint:** `GET /organizations/{tenant}/documents/{documentId}/status/history`\ **Authentication:** Tenant-level ```bash curl --request GET \ --url https://api.sandbox.inyoplatform.com/organizations/$TENANT/documents/$DOCUMENT_ID/status/history \ --header "x-api-key: $API_KEY" ``` #### List Person Documents by Status **Endpoint:** `GET /organizations/{tenant}/people/{personId}/documents`\ **Authentication:** Tenant-level ```bash curl --request GET \ --url https://api.sandbox.inyoplatform.com/organizations/$TENANT/people/$PERSON_ID/documents \ --header "x-api-key: $API_KEY" ``` *** ### Verification Statuses | Status | Description | | ------ | ----------- | | `Pending` | Document uploaded, awaiting review | | `Verified` | Document accepted β€” compliance level may upgrade | | `NotVerified` | Document rejected β€” see reason and re-upload | | `NotReadable` | Document image is unclear β€” request a better scan | | `TemplateIdentified` | Document type recognized but verification in progress | *** ### Webhook Notifications Register for the `documentUpdatedEvents` webhook to receive real-time notifications when a document's verification status changes. See [Webhooks](../webhooks.md) for setup instructions. *** ### Best Practices * **Upload high-quality images** β€” blurry or cropped images will be rejected as `NotReadable`. * **Upload both sides** when required β€” for documents like driver's licenses, upload both `FRONT` and `BACK`. * **Check compliance level after verification** β€” once a document is `Verified`, call `GET /participants/{id}/complianceLevels` to see if the sender has been upgraded. * **Handle rejections gracefully** β€” prompt the user to re-upload with clear instructions about what went wrong. * **Use webhooks** instead of polling β€” register for `documentUpdatedEvents` to be notified immediately when a document is reviewed. *** ### Test Data For sandbox testing, use the test data provided in [Test Data](test-data.md). Certain name/address combinations trigger specific compliance behaviors (approval, hold, rejection). [Interactive API Documentation β€” Document ID](https://dev-api.inyoglobal.com/sandbox/#tag/account_Person-Documents-Resource/paths/~1organizations~1%7Btenant%7D~1people~1%7BpersonId%7D~1documents~1documentId~1%7Bsubtype%7D~1upload/post)\ [Interactive API Documentation β€” Source of Funds](https://dev-api.inyoglobal.com/sandbox/#tag/account_Person-Documents-Resource/paths/~1organizations~1%7Btenant%7D~1people~1%7BpersonId%7D~1documents~1sourceOfFunds~1%7Bsubtype%7D~1upload/post) # Test data The following data can be used to test different compliance and transaction validation scenarios: | Parameter | Value | Result | Compliance Status | Payout Status | | -------------------- | ---------------- | -------------------------------------------------------- | ----------------- | ------------- | | address.zipcode | 99999 | Transaction will be REJECTED | Rejected | Cancelled | | phoneNumber | +14155557777 | Transaction will be REJECTED | Rejected | Cancelled | | firstName + lastName | BLOCK LIST MATCH | Transaction will be REJECTED | Rejected | Cancelled | | firstName + lastName | OFAC MATCH | Transaction will be put ON HOLD for further verification | Hold | Pending | # Sender Funding Account A Funding Account represents the source of funds used by the sender to pay for a transaction. Inyo supports multiple payment methods, all abstracted through a unified ledger system for compliance, reconciliation, and settlement tracking. *** ### Funding Account Statuses Every funding account has a `status` field that reflects its current verification state. Your application should handle all four statuses: | Status | Description | Can Be Used for Transactions? | | ------ | ----------- | ----------------------------- | | `Pending` | Account has been created but verification is not yet complete (e.g., waiting for ACH micro-deposit confirmation or initial processing). | ❌ No | | `ActionRequired` | Additional action is needed from the user β€” typically a 3D Secure (3DS) authentication challenge for card payments. See [Handling 3D Secure](../payments-gateway/apis/payment/pulling-funds/cards/authorizing/handling-3d-secure.md). | ❌ No | | `Verified` | Account has been fully verified and is ready to fund transactions. | βœ… Yes | | `Rejected` | Verification failed β€” the card was declined, 3DS authentication failed, or the bank account could not be verified. The sender must add a new funding account. | ❌ No | > πŸ“ **Note for API consumers:** The `status` field in funding account responses is returned as a string. The four values above are the complete set of possible statuses. #### Status Lifecycle ``` β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Pending β”‚ β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β–Ό β”‚ β–Ό β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ActionRequired β”‚ β”‚ β”‚ Verified β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β” β”‚ β–Ό β–Ό β–Ό β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Verified β”‚ β”‚ Rejected β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ``` - **Card (CARD):** Typically transitions `Pending` β†’ `ActionRequired` (3DS challenge) β†’ `Verified` or `Rejected`. Some low-risk cards may go directly to `Verified`. - **ACH:** Typically transitions `Pending` β†’ `Verified` or `Rejected` after bank account verification. - **Wallet (WALLET):** Usually transitions directly to `Verified`. > ⚠️ **Always check the status** before using a funding account in a transaction. Attempting to use a `Pending`, `ActionRequired`, or `Rejected` funding account will result in an error. > πŸ’‘ **Handling `Rejected`:** If a funding account is rejected, it cannot be retried. The sender must create a new funding account with corrected or alternative payment details. *** ### Supported Payment Methods | Method | Type Value | Description | Speed | | ------ | ---------- | ----------- | ----- | | Debit Card | `CARD` | Tokenized card payment via AFT (Account Funding Transaction) | Instant | | ACH | `ACH` | US bank transfer via the Automated Clearing House network | 1–3 business days | | Wallet | `WALLET` | Internal wallet / P2P balance | Instant | *** ### Creating a Funding Account **Endpoint:** `POST /organizations/{tenant}/payout/participants/{participantId}/fundingAccounts`\ **Authentication:** Agent-level (`x-api-key` + `x-agent-id` + `x-agent-api-key`) #### Common Request Fields | Field | Type | Required | Description | | ----- | ---- | -------- | ----------- | | `externalId` | string | No | Your internal reference for this funding source | | `asset` | string | Yes | Currency code (e.g., `USD`) | | `nickname` | string | No | Display name (e.g., "My Visa Debit") | | `paymentMethod` | object | Yes | Payment method details (varies by type) | *** #### Option A: Debit Card Card payments require a **tokenized card number**. Raw card details are never sent to the Inyo Core API β€” they are first sent to the tokenization service (see [Tokenizing Cards](../payments-gateway/apis/tokenizing-cards.md)) to obtain a secure token. ```bash curl --request POST \ --url https://api.sandbox.inyoplatform.com/organizations/$TENANT/payout/participants/$SENDER_ID/fundingAccounts \ --header 'Content-Type: application/json' \ --header "x-api-key: $API_KEY" \ --header "x-agent-id: $AGENT_ID" \ --header "x-agent-api-key: $AGENT_KEY" \ --data '{ "externalId": "funding-card-001", "asset": "USD", "nickname": "My Debit Card", "paymentMethod": { "type": "CARD", "token": "tok_12345_from_tokenizer", "billingAddress": { "countryCode": "US", "stateCode": "CA", "city": "San Francisco", "line1": "123 Market St", "zipcode": "94105" } } }' ``` **3D Secure (3DS) Challenge:** Some cards require additional authentication. If the response returns `status: 'ActionRequired'`: 1. Save the funding account locally with a `Pending` status 2. Display the provided `redirectAcsUrl` in an iframe for the user to complete the challenge 3. Listen for a `postMessage` event from the iframe confirming authorization 4. Call `GET /payout/fundingAccounts/{externalId}` to check the updated status 5. If `Verified` β†’ the card is ready to use for transactions 6. If `Rejected` β†’ the 3DS challenge failed or the card was declined; prompt the sender to add a different card For full 3DS implementation details, see [Handling 3D Secure](../payments-gateway/apis/payment/pulling-funds/cards/authorizing/handling-3d-secure.md). *** #### Option B: ACH (US Bank Account) For ACH funding, provide the routing and account number directly. Before registration, you must verify the bank account through an ACH verification partner to ensure the account is valid, has sufficient funds, and is not closed. ```bash curl --request POST \ --url https://api.sandbox.inyoplatform.com/organizations/$TENANT/payout/participants/$SENDER_ID/fundingAccounts \ --header 'Content-Type: application/json' \ --header "x-api-key: $API_KEY" \ --header "x-agent-id: $AGENT_ID" \ --header "x-agent-api-key: $AGENT_KEY" \ --data '{ "externalId": "funding-ach-001", "asset": "USD", "nickname": "My Checking", "paymentMethod": { "type": "ACH", "countryCode": "US", "bankCode": "US_ACH", "routingNumber": "123456789", "accountNumber": "000123456789", "accountType": "CHECKING" } }' ``` > ⚠️ **Save the returned `id`** β€” this is the `fundingAccountId` required when creating a transaction. *** ### Listing Funding Accounts **Endpoint:** `GET /organizations/{tenant}/payout/participants/{participantId}/fundingAccounts`\ **Authentication:** Agent-level ```bash curl --request GET \ --url https://api.sandbox.inyoplatform.com/organizations/$TENANT/payout/participants/$SENDER_ID/fundingAccounts \ --header "x-api-key: $API_KEY" \ --header "x-agent-id: $AGENT_ID" \ --header "x-agent-api-key: $AGENT_KEY" ``` Use this to show the sender their saved payment methods and let them select one for a transaction. *** ### Getting a Funding Account **Endpoint:** `GET /organizations/{tenant}/payout/participants/{participantId}/fundingAccounts/{fundingAccountId}`\ **Authentication:** Agent-level ```bash curl --request GET \ --url https://api.sandbox.inyoplatform.com/organizations/$TENANT/payout/participants/$SENDER_ID/fundingAccounts/$FUNDING_ACCOUNT_ID \ --header "x-api-key: $API_KEY" \ --header "x-agent-id: $AGENT_ID" \ --header "x-agent-api-key: $AGENT_KEY" ``` Use this to check a funding account's current status. Common use cases: - After a 3DS challenge to confirm `Verified` or detect `Rejected` - Before creating a transaction to ensure the funding account is still `Verified` - Polling after ACH account creation to check if verification has completed **Example response:** ```json { "id": "fa_abc123", "externalId": "funding-card-001", "asset": "USD", "nickname": "My Debit Card", "status": "Verified", "paymentMethod": { "type": "CARD", "lastFour": "4242", "brand": "VISA" }, "createdAt": "2025-02-18T15:30:00Z", "updatedAt": "2025-02-18T15:31:00Z" } ``` > πŸ’‘ **Tip:** Implement a polling strategy or webhook listener to detect status transitions from `Pending` β†’ `Verified`/`Rejected`, rather than relying on the user to manually refresh. *** ### How Funding Works in the Transaction Lifecycle ``` 1. Sender selects payment method β†’ Use existing or create new funding account 2. Transaction submitted β†’ Funds are debit-locked from the funding source 3. Compliance approved β†’ Funds are collected (captured) 4. Payout processed β†’ Funds settled to recipient via payout network 5. Transaction recorded β†’ Ledger entry created for reconciliation ``` *** ### All Endpoints | Operation | Method | Endpoint | | --------- | ------ | -------- | | Create funding account | `POST` | `/organizations/{tenant}/payout/participants/{participantId}/fundingAccounts` | | List funding accounts | `GET` | `/organizations/{tenant}/payout/participants/{participantId}/fundingAccounts` | | Get funding account | `GET` | `/organizations/{tenant}/payout/participants/{participantId}/fundingAccounts/{fundingAccountId}` | [Interactive API Documentation](https://dev-api.inyoglobal.com/sandbox/#tag/payout_Funding-Account-Resource) # Recipient A **Recipient** (beneficiary) is the person or company who receives the funds. Recipients are created using the same participant endpoints as senders β€” the system distinguishes roles based on how the `participantId` is used in a transaction (`recipientId` field). *** ### Schema-Driven Recipient Creation Recipient data requirements **vary by destination country**. Before collecting user data, always fetch the recipient schema: ```bash curl --request GET \ --url https://api.sandbox.inyoplatform.com/organizations/$TENANT/payout/recipients/schema/co \ --header "x-api-key: $API_KEY" \ --header "x-agent-id: $AGENT_ID" \ --header "x-agent-api-key: $AGENT_KEY" ``` This returns a JSON Schema (draft-07) defining exactly which fields are required for that country. See [Recipient Schema](data-population/recipient-schema.md) for details. *** ### Creating a Recipient **Endpoint:** `POST /organizations/{tenant}/people`\ **Authentication:** Tenant-level (`x-api-key`) #### Example: Brazilian Recipient ```bash curl --request POST \ --url https://api.sandbox.inyoplatform.com/organizations/$TENANT/people \ --header 'Content-Type: application/json' \ --header "x-api-key: $API_KEY" \ --data '{ "firstName": "Maria", "lastName": "Silva", "phoneNumber": "+5511999998888", "documents": [ { "type": "CPF", "document": "12345678901", "countryCode": "BR" } ] }' ``` #### Example: Colombian Recipient ```bash curl --request POST \ --url https://api.sandbox.inyoplatform.com/organizations/$TENANT/people \ --header 'Content-Type: application/json' \ --header "x-api-key: $API_KEY" \ --data '{ "firstName": "Carlos", "lastName": "Gutierrez", "phoneNumber": "+573001234567", "address": { "countryCode": "CO", "city": "BogotΓ‘", "line1": "Calle 100 #15-20" }, "documents": [ { "type": "CC", "document": "1234567890", "countryCode": "CO" } ] }' ``` > ⚠️ **Save the returned `id`** β€” this is the `recipientId` used in the transaction. *** ### Country-Specific Document Types | Country | Document Type | Code | Format | | ------- | ------------- | ---- | ------ | | Brazil | CPF (Cadastro de Pessoas FΓ­sicas) | `CPF` | 11 digits | | Colombia | CΓ©dula de CiudadanΓ­a | `CC` | 8–10 digits | | Mexico | CURP / INE | Varies | Per schema | | Peru | DNI (Documento Nacional de Identidad) | `DNI` | 8 digits | | India | PAN / Aadhaar | Varies | Per schema | | USA | SSN / ITIN | `SSN`, `ITIN` | 9 digits | > πŸ’‘ Always consult the recipient schema for the authoritative list of accepted document types per country. *** ### Reusing Participants Participants are reusable entities: * A person created as a **recipient** can later be used as a **sender** (if they meet compliance requirements) * A person can act as **both sender and recipient** in the same transaction (self-send) by passing the same `participantId` for both `senderId` and `recipientId` * A participant only needs to be created **once** and can be referenced in unlimited future transactions *** ### After Creating the Recipient Once you have the `recipientId`, the next step is to link a bank account: 1. **Fetch the account schema** β€” `GET /payout/recipientAccounts/schema/{countryCode}` 2. **Fetch the bank list** (if required) β€” `GET /payout/{countryCode}/banks` 3. **Create the recipient account** β€” `POST /payout/participants/{recipientId}/recipientAccounts/gateway` See [Recipient Account](recipient-account.md) for full details. *** ### Best Practices * **Always use the schema endpoint** to determine required fields β€” don't hardcode field requirements per country. * **Dynamically render forms** based on the schema response to automatically support new countries. * **Collect only required fields** β€” submitting unnecessary data may trigger additional compliance checks. * **Validate input client-side** using the schema's `pattern`, `minLength`, and `enum` constraints before submitting. * **Handle the `address` requirement** β€” some countries require a recipient address, others don't. The schema is your source of truth. [Interactive API Documentation](https://dev-api.inyoglobal.com/sandbox/#tag/account_Person-Resource) # Recipient Account A Recipient Account represents the bank account or payout destination where funds will be delivered. Before creating a recipient account, you must first [create the recipient](recipient.md) as a participant and [fetch the account schema](data-population/recipient-account-schema.md) for the destination country. *** ### Schema-Driven Creation Recipient account requirements **vary by country**. Always fetch the account schema before building your form or API call: ```bash # Fetch account schema for Colombia curl --request GET \ --url https://api.sandbox.inyoplatform.com/organizations/$TENANT/payout/recipientAccounts/schema/co \ --header "x-api-key: $API_KEY" \ --header "x-agent-id: $AGENT_ID" \ --header "x-agent-api-key: $AGENT_KEY" ``` The schema returns a JSON Schema (draft-07) object that defines required fields, allowed values, and validation rules. Common fields include: | Field | Description | Examples | | ----- | ----------- | -------- | | `asset` | Currency code (ISO 4217) | `BRL`, `MXN`, `COP`, `PEN` | | `payoutMethod.type` | Payout mechanism | `BANK_DEPOSIT` | | `payoutMethod.countryCode` | Destination country | `BR`, `MX`, `CO` | | `payoutMethod.bankCode` | Bank identifier | Varies by country (use [Banks endpoint](data-population/banks-in-a-country.md)) | | `payoutMethod.routingNumber` | Routing/SWIFT/BIC code | Country-specific | | `payoutMethod.accountNumber` | Account number | CLABE (MX), IBAN, or local account number | | `payoutMethod.accountType` | Account type | `CHECKING`, `SAVINGS` | > πŸ’‘ **Tip:** For countries that require a `bankCode`, use the [Banks in a Country](data-population/banks-in-a-country.md) endpoint to populate a dropdown in your UI. *** ### Creating a Recipient Account **Endpoint:** `POST /organizations/{tenant}/payout/participants/{participantId}/recipientAccounts/gateway`\ **Authentication:** Agent-level (`x-api-key` + `x-agent-id` + `x-agent-api-key`) #### Request Body | Field | Type | Required | Description | | ----- | ---- | -------- | ----------- | | `externalId` | string | No | Your internal identifier for this account | | `asset` | string | Yes | Currency code (ISO 4217) for the recipient's account | | `payoutMethod` | object | Yes | Bank account details (country-specific, see schema) | #### Example: Colombia (Bank Deposit) ```bash curl --request POST \ --url https://api.sandbox.inyoplatform.com/organizations/$TENANT/payout/participants/$RECIPIENT_ID/recipientAccounts/gateway \ --header 'Content-Type: application/json' \ --header "x-api-key: $API_KEY" \ --header "x-agent-id: $AGENT_ID" \ --header "x-agent-api-key: $AGENT_KEY" \ --data '{ "externalId": "acct-co-001", "asset": "COP", "payoutMethod": { "type": "BANK_DEPOSIT", "countryCode": "CO", "bankCode": "1001", "routingNumber": "1234", "accountNumber": "9876543210", "accountType": "SAVINGS" } }' ``` #### Example: Mexico (CLABE) ```bash curl --request POST \ --url https://api.sandbox.inyoplatform.com/organizations/$TENANT/payout/participants/$RECIPIENT_ID/recipientAccounts/gateway \ --header 'Content-Type: application/json' \ --header "x-api-key: $API_KEY" \ --header "x-agent-id: $AGENT_ID" \ --header "x-agent-api-key: $AGENT_KEY" \ --data '{ "externalId": "acct-mx-001", "asset": "MXN", "payoutMethod": { "type": "BANK_DEPOSIT", "countryCode": "MX", "bankCode": "002", "routingNumber": "002", "accountNumber": "012345678901234567", "accountType": "CHECKING" } }' ``` #### Example: Peru (Bank Deposit) ```bash curl --request POST \ --url https://api.sandbox.inyoplatform.com/organizations/$TENANT/payout/participants/$RECIPIENT_ID/recipientAccounts/gateway \ --header 'Content-Type: application/json' \ --header "x-api-key: $API_KEY" \ --header "x-agent-id: $AGENT_ID" \ --header "x-agent-api-key: $AGENT_KEY" \ --data '{ "externalId": "acct-pe-001", "asset": "PEN", "payoutMethod": { "type": "BANK_DEPOSIT", "countryCode": "PE", "bankCode": "BINPPEPL", "routingNumber": "1234", "accountNumber": "1345", "accountType": "CHECKING" } }' ``` > ⚠️ **Save the returned `id`** β€” this is the `recipientAccountId` required when creating a transaction. *** ### Getting a Recipient Account **Endpoint:** `GET /organizations/{tenant}/payout/participants/{participantId}/recipientAccounts/{recipientAccountId}`\ **Authentication:** Agent-level ```bash curl --request GET \ --url https://api.sandbox.inyoplatform.com/organizations/$TENANT/payout/participants/$RECIPIENT_ID/recipientAccounts/$ACCOUNT_ID \ --header "x-api-key: $API_KEY" \ --header "x-agent-id: $AGENT_ID" \ --header "x-agent-api-key: $AGENT_KEY" ``` *** ### Deleting a Recipient Account **Endpoint:** `DELETE /organizations/{tenant}/payout/participants/{participantId}/recipientAccounts/{recipientAccountId}`\ **Authentication:** Agent-level ```bash curl --request DELETE \ --url https://api.sandbox.inyoplatform.com/organizations/$TENANT/payout/participants/$RECIPIENT_ID/recipientAccounts/$ACCOUNT_ID \ --header "x-api-key: $API_KEY" \ --header "x-agent-id: $AGENT_ID" \ --header "x-agent-api-key: $AGENT_KEY" ``` *** ### Country-Specific Requirements | Country | Currency | Key Field | Document Type | Notes | | ------- | -------- | --------- | ------------- | ----- | | Brazil (BR) | BRL | `accountNumber` | CPF | PIX keys may be supported depending on your corridor config | | Mexico (MX) | MXN | `accountNumber` (CLABE, 18 digits) | CURP / INE | CLABE is the standard interbank identifier | | Colombia (CO) | COP | `bankCode` + `accountNumber` | CC (CΓ©dula) | Use the Banks endpoint for valid `bankCode` values | | Peru (PE) | PEN | `bankCode` + `accountNumber` | DNI | Bank codes use SWIFT/BIC format | | India (IN) | INR | `routingNumber` (IFSC) | PAN / Aadhaar | IFSC code is mandatory for Indian bank transfers | | Philippines (PH) | PHP | `bankCode` + `accountNumber` | β€” | β€” | > πŸ’‘ Always use the schema endpoint as the source of truth β€” the table above is for guidance only. *** ### Integration Workflow ``` 1. Fetch account schema β†’ GET /payout/recipientAccounts/schema/{countryCode} 2. Fetch bank list β†’ GET /payout/{countryCode}/banks (if bankCode is required) 3. Collect user input β†’ Build form from schema 4. Create account β†’ POST /payout/participants/{id}/recipientAccounts/gateway 5. Save account ID β†’ Use in POST /fx/transactions ``` *** ### All Endpoints | Operation | Method | Endpoint | | --------- | ------ | -------- | | Create recipient account | `POST` | `/organizations/{tenant}/payout/participants/{participantId}/recipientAccounts/gateway` | | Get recipient account | `GET` | `/organizations/{tenant}/payout/participants/{participantId}/recipientAccounts/{recipientAccountId}` | | Delete recipient account | `DELETE` | `/organizations/{tenant}/payout/participants/{participantId}/recipientAccounts/{recipientAccountId}` | [Interactive API Documentation](https://dev-api.inyoglobal.com/sandbox/#tag/payout_Recipient-Account-Resource) # Transaction The Transaction endpoint is the culmination of the remittance flow. It links together the sender, recipient, funding source, recipient account, and quote to execute a cross-border payment. *** ### Creating a Transaction **Endpoint:** `POST /organizations/{tenant}/fx/transactions`\ **Authentication:** Agent-level (`x-api-key` + `x-agent-id` + `x-agent-api-key`) #### Request Body | Field | Type | Required | Description | | -------------------------- | ------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | | `senderId` | string (UUID) | Yes | The participant ID of the sender (from `POST /people`) | | `recipientId` | string (UUID) | Yes | The participant ID of the recipient (from `POST /people`) | | `fundingAccountId` | string (UUID) | Yes | The sender's funding source (from `POST /fundingAccounts`) | | `recipientAccountId` | string (UUID) | Yes | The recipient's bank account (from `POST /recipientAccounts/gateway`) | | `quoteId` | string (UUID) | Yes | The locked FX quote (from `POST /payout/quotes`) | | `externalId` | string (UUID) | No | Your internal reference ID for idempotency and reconciliation | | `additionalData` | object | Yes | Country-specific data (see [Transaction Schema](data-population/transaction-schema.md)). Pass `{}` if no additional data is required for the corridor. | | `deviceData` | object | No | Device fingerprinting data for fraud prevention | | `deviceData.userIpAddress` | string | Recommended | The end user's IP address | #### Sample Request ```bash curl --request POST \ --url https://api.sandbox.inyoplatform.com/organizations/$TENANT/fx/transactions \ --header 'Content-Type: application/json' \ --header "x-agent-id: $AGENT_ID" \ --header "x-agent-api-key: $AGENT_KEY" \ --data '{ "senderId": "422a55b2-78c3-4509-8654-16e7375d3e40", "recipientId": "4b9dcb4e-2e2a-4e96-b2a9-fcd868b2f4ed", "fundingAccountId": "3fe5f23f-65d3-41b6-b34c-33db3c4d8ef9", "recipientAccountId": "67203d69-dc48-41d4-b2ac-52682d39b032", "quoteId": "30bf05d2-b416-4e71-b8e8-1ef158a2b414", "additionalData": {}, "deviceData": { "userIpAddress": "200.123.131.112" } }' ``` #### Sample Response (HTTP 202) ```json { "id": "9035ee18-052b-4176-a940-ccfe599a1829", "sender": { "id": "ce06773d-6472-4c93-bdf5-8eb68ab01c7f", "type": "PERSON", "name": "Rob Dalas", "phoneNumber": "+1 123 456435" }, "recipient": { "id": "156675d5-1d1b-4b4d-997e-5c15ae129b82", "type": "PERSON", "name": "Bob Danilo", "phoneNumber": "+55 71 91234-5678" }, "tenantId": "master", "externalId": null, "agentId": "41f55211-1eea-4747-9a03-68e99f07ede5", "createdAt": "2025-05-29T17:26:27.264312", "anchorCurrencyAmount": { "amount": "102.53", "currency": "USD" }, "complianceStatus": "Approved", "payoutStatus": "Pending", "status": { "id": "0097c1fb-56ac-4314-afa1-eaf00e712d5b", "transactionId": "e58796a9-e809-4503-8ce3-2c96c0a0e170", "complianceStatus": "Approved", "payoutStatus": "Pending", "messages": [ { "id": "7122d9ab-2e60-41a4-94aa-e4231b1ca4f7", "message": "STANDARD - PAYMENT_RECEIVED", "createdBy": "system", "createdAt": "2025-11-21T11:48:10.268817", "type": "SystemUpdate" } ], "createdAt": "2025-11-21T14:48:05.891186" }, "quoteId": "af2d55d8-1d40-426a-8d4a-7454ccdcbe8a", "fundingAccountId": "61bc14a1-b3a2-40b4-b395-0496e4f554a1", "recipientAccountId": "4fbc57a6-f673-44ba-88aa-c6e71eb9181b", "exchangeRate": "5.571062480000000", "totalAmount": { "amount": "102.53", "currency": "USD" }, "receivingAmount": { "amount": "571.20", "currency": "BRL" }, "fee": { "amount": "1.54", "currency": "USD" }, "conversionAmount": { "amount": "102.53", "currency": "USD" }, "originExchangeRate": { "fromCurrency": "USD", "toCurrency": "USD", "rate": "1.000000000000000" }, "destinationExchangeRate": { "fromCurrency": "USD", "toCurrency": "BRL", "rate": "5.571062480000000" }, "type": "FX", "senderId": "ce06773d-6472-4c93-bdf5-8eb68ab01c7f", "recipientId": "156675d5-1d1b-4b4d-997e-5c15ae129b82" } ``` #### Response Fields | Field | Description | | ------------------------- | ------------------------------------------------------------------ | | `id` | Unique transaction ID | | `complianceStatus` | Current compliance screening result | | `payoutStatus` | Current payout delivery status | | `status.messages` | Audit trail of status transitions with timestamps | | `exchangeRate` | The applied FX rate | | `totalAmount` | Total amount charged to the sender (in source currency) | | `receivingAmount` | Amount to be received by the beneficiary (in destination currency) | | `fee` | Transaction fee in source currency | | `conversionAmount` | Amount that was converted (source currency) | | `originExchangeRate` | Exchange rate from source currency to anchor currency | | `destinationExchangeRate` | Exchange rate from anchor currency to destination currency | | `type` | Transaction type: `FX`, `TOP_UP`, or `BILLPAY` | *** ### Transaction Status Lifecycle Every transaction has two independent status dimensions: #### Compliance Status | Status | Description | | ---------- | ------------------------------------------------------ | | `Approved` | Passed all compliance checks β€” transaction can proceed | | `Hold` | Flagged for manual review by the compliance team | | `Rejected` | Failed compliance screening β€” transaction is blocked | #### Payout Status | Status | Description | | ----------- | ------------------------------------------------------------------- | | `Pending` | Transaction accepted, awaiting processing | | `Paid` | Funds successfully delivered to the recipient | | `Cancelled` | Transaction was cancelled (by user request or compliance rejection) | | `Failed` | Transaction was failed (by payment gateway) | | `Refunded` | Transaction was refunded | #### Compliance Status Transitions ``` β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”‚ │─────┐ β”‚ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β” β”‚ β”‚β”Œβ”€β”€β”€β”€β”‚ Hold │────┐│ β”‚β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚β”‚ β”‚β”‚ β”‚β”‚ β”Œβ”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β–Όβ–Όβ”€β”€β”€β”€β” β”‚ Rejected β”‚ β”‚ Approved β”‚ β”‚(Cancelled)β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ``` *** ### Querying Transactions #### Get Transaction by ID ```bash curl --request GET \ --url https://api.sandbox.inyoplatform.com/organizations/$TENANT/fx/transactions/$TRANSACTION_ID \ --header "x-api-key: $API_KEY" ``` #### List Transactions with Filters ```bash curl --request GET \ --url "https://api.sandbox.inyoplatform.com/organizations/$TENANT/fx/transactions?page=0&size=20&type=FX&complianceStatus=Approved" \ --header "x-api-key: $API_KEY" ``` **Query Parameters:** | Parameter | Type | Description | | ------------------ | ------- | ----------------------------------------- | | `page` | integer | Page number (0-indexed) | | `size` | integer | Results per page | | `type` | string | Filter by type: `FX`, `TOP_UP`, `BILLPAY` | | `complianceStatus` | string | Filter by compliance status | | `payoutStatus` | string | Filter by payout status | | `senderName` | string | Filter by sender name | | `receiverName` | string | Filter by receiver name | | `from` | string | Start date filter | | `to` | string | End date filter | #### Get Transaction Status History Returns the full audit trail of status changes for a transaction. ```bash curl --request GET \ --url https://api.sandbox.inyoplatform.com/organizations/$TENANT/fx/transactions/$TRANSACTION_ID/status \ --header "x-api-key: $API_KEY" ``` **Sample Response:** ```json [ { "id": "2f6b3f12-a63f-43f7-9bb4-4e98dfc75fe5", "complianceStatus": "Approved", "payoutStatus": "Pending", "transactionId": "46b4c393-3768-4ba4-b6d2-a6398e17dd78", "tenantId": "master", "externalId": "GMT000648512199", "createdAt": "2024-01-03T12:54:09.455", "messages": [ { "id": "0fbbddb8-283d-466d-b2e6-42dcbb3e67de", "message": "STANDARD - PAYMENT_RECEIVED", "createdBy": "system", "createdAt": "2025-11-19T13:29:52.221882", "type": "SystemUpdate" } ] } ] ``` *** ### Cancelling a Transaction Cancel a transaction that has not yet been paid out. Cancellation is allowed when the payout status is `Pending`. **Endpoint:** `PUT /organizations/{tenant}/fx/transactions/{transactionId}/status/cancel`\ **Authentication:** Tenant-level (`x-api-key`) ```bash curl --request PUT \ --url https://api.sandbox.inyoplatform.com/organizations/$TENANT/fx/transactions/$TRANSACTION_ID/status/cancel \ --header "x-api-key: $API_KEY" ``` **Responses:** | Status | Description | | ------ | --------------------------------------------------------- | | `204` | Transaction successfully cancelled | | `400` | Cannot cancel β€” transaction is in a non-cancellable state | | `404` | Transaction not found | > ⚠️ Once a transaction reaches `Paid` status, it cannot be cancelled β€” use the refund endpoint instead. *** ### Refunding a Transaction Refund a transaction that has already been paid. **Endpoint:** `PUT /organizations/{tenant}/fx/transactions/{transactionId}/status/refund`\ **Authentication:** Tenant-level (`x-api-key`) ```bash curl --request PUT \ --url https://api.sandbox.inyoplatform.com/organizations/$TENANT/fx/transactions/$TRANSACTION_ID/status/refund \ --header "x-api-key: $API_KEY" ``` **Responses:** | Status | Description | | ------ | ---------------------------------------------------------- | | `204` | Refund initiated | | `400` | Cannot refund β€” transaction is not in `Paid` payout status | | `404` | Transaction not found | *** ### Updating Transaction Metadata Attach arbitrary key-value data to a transaction for your internal tracking. **Endpoint:** `PUT /organizations/{tenant}/fx/transactions/{transactionId}/metadata`\ **Authentication:** Tenant-level (`x-api-key`) ```bash curl --request PUT \ --url https://api.sandbox.inyoplatform.com/organizations/$TENANT/fx/transactions/$TRANSACTION_ID/metadata \ --header 'Content-Type: application/json' \ --header "x-api-key: $API_KEY" \ --data '{ "internalRef": "INV-2025-001", "department": "treasury" }' ``` *** ### Transaction Limits Before executing a transaction, verify the sender has not exceeded their limits. **Endpoint:** `GET /organizations/{tenant}/fx/participants/{participantId}/limits`\ **Authentication:** Agent-level ```bash curl --request GET \ --url https://api.sandbox.inyoplatform.com/organizations/$TENANT/fx/participants/$SENDER_ID/limits \ --header "x-agent-id: $AGENT_ID" \ --header "x-agent-api-key: $AGENT_KEY" ``` **Sample Response:** ```json { "oneDayLimit": { "limit": { "amount": "2999.00", "currency": "USD" }, "used": { "amount": "0.00", "currency": "USD" }, "available": { "amount": "2999.00", "currency": "USD" } }, "thirtyDaysLimit": { "limit": { "amount": "6000.00", "currency": "USD" }, "used": { "amount": "2959.99", "currency": "USD" }, "available": { "amount": "3040.01", "currency": "USD" } }, "oneHundredAndEightyDaysLimit": { "limit": { "amount": "9999.00", "currency": "USD" }, "used": { "amount": "5000.00", "currency": "USD" }, "available": { "amount": "4999.00", "currency": "USD" } }, "complianceLevel": { "level": "LEVEL_1", "requiredFields": ["birthDate", "lastName", "name", "occupation", "phoneNumber", "residentialAddress"] } } ``` *** ### Receipts (Regulatory Requirement) As an agent of a licensed Money Service Business, you are **legally required** to issue a receipt to the sender immediately after transaction submission. The transaction response includes a `receipt` object with mandatory regulatory disclosures that **must be displayed verbatim** to the end user. The receipt must include: 1. **Exchange rate** β€” the locked-in rate from the quote 2. **Fees** β€” total fees charged to the customer 3. **Total amount** β€” full amount paid by the sender 4. **Receive amount** β€” exact amount to be received by the beneficiary 5. **Regulatory disclosures** β€” dynamic legal text specific to the corridor (e.g., right to refund, cancellation policy) > ⚠️ You **must not** alter the financial data or regulatory text returned by the API. Display it as-is. *** ### Error Responses | HTTP Status | Error | Cause | | ----------- | -------------------- | ------------------------------------------------------------------ | | `400` | Bad request | Missing required fields, invalid data format, or expired quote | | `403` | Forbidden | Agent not approved, or sender doesn't meet compliance requirements | | `422` | Unprocessable entity | Business rule violation (e.g., limit exceeded, invalid corridor) | *** ### Additional Endpoints | Operation | Method | Endpoint | | ---------------------- | ------ | ----------------------------------------------------------------------- | | Create transaction | `POST` | `/organizations/{tenant}/fx/transactions` | | Get transaction | `GET` | `/organizations/{tenant}/fx/transactions/{transactionId}` | | List transactions | `GET` | `/organizations/{tenant}/fx/transactions` | | Get status history | `GET` | `/organizations/{tenant}/fx/transactions/{transactionId}/status` | | Cancel transaction | `PUT` | `/organizations/{tenant}/fx/transactions/{transactionId}/status/cancel` | | Refund transaction | `PUT` | `/organizations/{tenant}/fx/transactions/{transactionId}/status/refund` | | Update metadata | `PUT` | `/organizations/{tenant}/fx/transactions/{transactionId}/metadata` | | Check limits | `GET` | `/organizations/{tenant}/fx/participants/{participantId}/limits` | | Get transaction schema | `GET` | `/organizations/{tenant}/fx/transactions/schema?countryCode={iso2}` | [Interactive API Documentation](https://dev-api.inyoglobal.com/sandbox/#tag/fx_Transaction-Resource) # Webhooks Webhooks allow you to receive real-time notifications when important events occur in the Inyo platform β€” such as a transaction changing status from `Pending` to `Paid`, or a document verification completing. Instead of polling the API, you register a callback URL and Inyo pushes events to you. *** ### Supported Events | Event | Description | When It Fires | | ------------------------------------ | ----------------------------------------------- | -------------------------------------------------------------- | | `TransactionStatusChanged` | A transaction's payout status has changed | Status transitions (e.g., Pending β†’ Paid, Pending β†’ Cancelled) | | `documentUpdatedEvents` | A document verification status has been updated | After compliance team reviews an uploaded document | | `agentStatusChangedEvent` | An agent's status or configuration has changed | Agent approval, suspension, or configuration updates | | `TransactionComplianceStatusChanged` | A transaction's compliance status has changed | Status transitions (e.g., Hold β†’ Approved, Hold β†’ Rejected) | *** ### Registering a Webhook **Endpoint:** `POST /organizations/{tenant}/webhooks`\ **Authentication:** Tenant-level (`x-api-key`) ```bash curl --request POST \ --url https://api.sandbox.inyoplatform.com/organizations/$TENANT/webhooks \ --header 'Content-Type: application/json' \ --header "x-api-key: $API_KEY" \ --data '{ "events": [ "transactionStatusChanged", "documentUpdatedEvents", "agentUpdatedEvents" ], "url": "https://your-server.com/api/webhooks/inyo" }' ``` > πŸ’‘ You can register for all events or only the ones relevant to your integration. *** ### Listing Registered Webhooks **Endpoint:** `GET /organizations/{tenant}/webhooks`\ **Authentication:** Tenant-level (`x-api-key`) ```bash curl --request GET \ --url https://api.sandbox.inyoplatform.com/organizations/$TENANT/webhooks \ --header "x-api-key: $API_KEY" ``` *** ### Deleting a Webhook **Endpoint:** `DELETE /organizations/{tenant}/webhooks/{webhookId}`\ **Authentication:** Tenant-level (`x-api-key`) ```bash curl --request DELETE \ --url https://api.sandbox.inyoplatform.com/organizations/$TENANT/webhooks/$WEBHOOK_ID \ --header "x-api-key: $API_KEY" ``` *** ### Webhook Payload When an event occurs, Inyo sends an HTTP `POST` request to your registered URL with a JSON payload. Your endpoint should: 1. Accept `POST` requests with `Content-Type: application/json` 2. Return an HTTP `2xx` response to acknowledge receipt 3. Process the event asynchronously (don't block the response) #### Example: Transaction Status Changed ```json { "event": "transactionStatusChanged", "transactionId": "9035ee18-052b-4176-a940-ccfe599a1829", "tenantId": "your_tenant", "complianceStatus": "Approved", "payoutStatus": "Paid", "externalId": "GMT000648512199", "timestamp": "2025-11-21T14:48:05.891186Z", "messages": [ { "message": "STANDARD - PAYMENT_DELIVERED", "createdBy": "system", "type": "SystemUpdate" } ] } ``` *** ### Best Practices #### Idempotency Your webhook handler should be **idempotent** β€” the same event may be delivered more than once. Use the `transactionId` and event data to deduplicate. #### Respond Quickly Return a `200 OK` as soon as you receive the webhook. Perform heavy processing (database updates, notifications, etc.) asynchronously. If your endpoint takes too long to respond, the delivery may be retried. #### Secure Your Endpoint * **Use HTTPS** β€” always register an `https://` URL for your webhook endpoint. * **Validate the source** β€” consider IP allowlisting or implementing shared-secret verification if supported by your integration agreement. * **Don't trust the payload blindly** β€” after receiving a `transactionStatusChanged` event, fetch the transaction via `GET /fx/transactions/{id}` to confirm the current status before taking action. #### Handle Failures Gracefully If your endpoint returns a non-2xx response or is unreachable, Inyo will retry delivery. Design your system to handle: * Duplicate deliveries (idempotency) * Out-of-order deliveries (check timestamps) * Delayed deliveries (don't assume real-time) *** ### Integration Example Here's a minimal Node.js webhook handler: ```javascript const express = require('express'); const app = express(); app.use(express.json()); app.post('/api/webhooks/inyo', async (req, res) => { // Acknowledge immediately res.status(200).send('OK'); const { event, transactionId, complianceStatus, payoutStatus } = req.body; console.log(`[Webhook] ${event} β€” Transaction: ${transactionId}`); console.log(` Compliance: ${complianceStatus}, Payout: ${payoutStatus}`); // Process asynchronously if (event === 'transactionStatusChanged') { if (payoutStatus === 'Paid') { // Notify sender, update your database, generate receipt await markTransactionComplete(transactionId); } else if (complianceStatus === 'Rejected') { // Handle rejection β€” notify sender, initiate refund if needed await handleRejection(transactionId); } } }); app.listen(3001, () => console.log('Webhook listener on :3001')); ``` *** ### All Endpoints | Operation | Method | Endpoint | | ---------------- | -------- | ---------------------------------------------- | | Register webhook | `POST` | `/organizations/{tenant}/webhooks` | | List webhooks | `GET` | `/organizations/{tenant}/webhooks` | | Delete webhook | `DELETE` | `/organizations/{tenant}/webhooks/{webhookId}` | [Interactive API Documentation](https://dev-api.inyoglobal.com/sandbox/#tag/webhook_Webhooks-Resource) # Assets Some products require regulatory disclosures to be presented to consumers. We’ve simplified this process by providing easily embeddable resources, including markdown-ready metadata and logos in multiple sizes and formats for seamless integration. | πŸ›‘οΈ Compliance Statement | [View Compliance Statement](https://terms.inyoglobal.com/compliance-statement.md) | | ------------------------ | --------------------------------------------------------------------------------- | | πŸ” Privacy Policy | [View Privacy Policy](https://terms.inyoglobal.com/privacy-policy.md) | | πŸ›οΈ State Licenses | [View State Licenses](https://terms.inyoglobal.com/state-license.md) | | πŸ“„ Terms of Service | [View Terms of Service](https://terms.inyoglobal.com/terms-of-service.md) | | :balloon:Inyo logos | [SVG - white and purple](https://terms.inyoglobal.com/logo-inyo-svg.zip) |