# Get Access Token Source: https://docs.ripio.com/crypto-as-a-service/access-token/get-access-token crypto-as-a-service/openapi.json POST /oauth2/token/ Every call to the API has to be authenticated with an OAuth2 Token. In order to request this token, you will need to have sandbox or production API Keys (`client_id` and `client_secret`) that will allow to negotiate an ephemeral access token. To generate the access token, you should call this endpoint providing the header `Authorization` with the following schema: `Basic CREDENTIAL` where `CREDENTIAL` should be specified as a string with the format`client_id:client_secret` encoded in Base64. # Create Address for End User Source: https://docs.ripio.com/crypto-as-a-service/addresses/create-address-for-end-user crypto-as-a-service/openapi.json POST /api/v1/end-users/{end_user_id}/addresses/ Endpoint that allows the creation of a deposit address for a given end user. # List End User's Addresses Source: https://docs.ripio.com/crypto-as-a-service/addresses/list-end-users-addresses crypto-as-a-service/openapi.json GET /api/v1/end-users/{end_user_id}/addresses/ This endpoint allows to fetch a list of the given user's wallet's addresses # Create Auto Swap Address for End User Source: https://docs.ripio.com/crypto-as-a-service/auto-swap-addresses/create-auto-swap-address-for-end-user crypto-as-a-service/openapi.json POST /api/v1/end-users/{end_user_id}/auto-swap-addresses/ Endpoint that allows the creation of a auto swap deposit address for a given end user. This endpoint behaves idempontently for a given user and any of the networks from a network family. Auto Swap is a feature that allows you to create a deposit address, and any deposit you make will be automatically swapped for a pre-defined asset (contact the admin to set this value) and then withdrawn to the desired target address. Important Notes: - The swap will be executed at the best market price obtained after your deposit is confirmed. In other words, this is an open-price model, so there are no guarantees or held-quotes on the final price obtained before the deposit happens. - Trading and withdrawal fees will be deducted before the converted funds are sent to your target address. # List End User's Auto Swap Addresses Source: https://docs.ripio.com/crypto-as-a-service/auto-swap-addresses/list-end-users-auto-swap-addresses crypto-as-a-service/openapi.json GET /api/v1/end-users/{end_user_id}/auto-swap-addresses/ This endpoint allows to fetch a list of the given user's auto-swap addresses # Update target_address of EndUser's AutoSwap address. Source: https://docs.ripio.com/crypto-as-a-service/auto-swap-addresses/update-target_address-of-endusers-autoswap-address crypto-as-a-service/openapi.json PUT /api/v1/end-users/{end_user_id}/auto-swap-addresses/ This endpoint allows to update the target_address of the given EndUser's auto-swap addresses # Get Balances for End User Source: https://docs.ripio.com/crypto-as-a-service/balance/get-balances-for-end-user crypto-as-a-service/openapi.json GET /api/v1/end-users/{end_user_id}/balances/ Retrieve end_user balances details given an end_user_id (external_ref). # Create Beneficiary Fiat Account Source: https://docs.ripio.com/crypto-as-a-service/beneficiary-fiat-accounts/create-beneficiary-fiat-account crypto-as-a-service/openapi.json POST /api/v1/end-users/{end_user_id}/beneficiary-fiat-accounts/ This endpoint allows you to create an Beneficiary Fiat Account. An Beneficiary Fiat Account represents a Fiat Account owned by the given end user, and will be used to [withdrawal fiat money](/crypto-as-a-service/fiat-withdrawals/create-fiat-withdrawal) from user's balance. # List Beneficiary Fiat Account Source: https://docs.ripio.com/crypto-as-a-service/beneficiary-fiat-accounts/list-beneficiary-fiat-account crypto-as-a-service/openapi.json GET /api/v1/end-users/{end_user_id}/beneficiary-fiat-accounts/ This endpoint allows to fetch a list of the given user's beneficiary fiat accounts. # Retrieve Beneficiary Fiat Account Source: https://docs.ripio.com/crypto-as-a-service/beneficiary-fiat-accounts/retrieve-beneficiary-fiat-account crypto-as-a-service/openapi.json GET /api/v1/end-users/{end_user_id}/beneficiary-fiat-accounts/{beneficiary_fiat_account_id} This endpoint allows to retrieve an end user's beneficiary fiat accounts details. # List Daily Yield Accumulations Source: https://docs.ripio.com/crypto-as-a-service/daily-yield-accumulations/list-daily-yield-accumulations crypto-as-a-service/openapi.json GET /api/v1/daily-yield-accumulations/ List the daily yield accumulations for your account: a per-asset, per-day aggregate of all yield credited to your end users. Each accumulation includes its fiat valuation (`fiat_details`, ARS by default), captured at the daily run moment. Supports filtering by asset and by accrual date range. # Retrieve Daily Yield Accumulation Source: https://docs.ripio.com/crypto-as-a-service/daily-yield-accumulations/retrieve-daily-yield-accumulation crypto-as-a-service/openapi.json GET /api/v1/daily-yield-accumulations/{daily_yield_accumulation_id}/ Retrieve a specific daily yield accumulation, including the total accrued amount, the Ripio and partner fees charged, and the number of contributing end users, and the fiat valuation (`fiat_details`) for the asset on the accrued date. # Create Deposit IPN Notification for Testing Source: https://docs.ripio.com/crypto-as-a-service/deposit-ipn/create-deposit-ipn-notification-for-testing crypto-as-a-service/openapi.json POST /api/v1/ipn/test/ Endpoint that allows the creation of a dummy (but with proper format) deposit IPN Notification that will be sent to the registered IPN Webhooks created before. # List Deposits Source: https://docs.ripio.com/crypto-as-a-service/deposits/list-deposits crypto-as-a-service/openapi.json GET /api/v1/deposits/ Endpoint that list all the deposits received. **Note**: This endpoints allows to poll or reconcile deposited transactions. We offer the capability to set a Webhook URL in order to be notified in real time. Please note that you should expect a payload format similar to the one below (see results attribute in response's payload) **Ordering**: Results are returned in ascending creation order, the oldest deposit first. The order is stable across requests and pages, so paging through the whole list never repeats nor skips a deposit. # Retrieve Deposit Source: https://docs.ripio.com/crypto-as-a-service/deposits/retrieve-deposit crypto-as-a-service/openapi.json GET /api/v1/deposits/{deposit_id}/ Endpoint that retrieves details of a received deposit by id. **Note**: We offer the capability to set a Webhook URL in order to be notified in real time. Please note that you should expect a payload format similar to the one below. # Assign Billing Information for an End User Source: https://docs.ripio.com/crypto-as-a-service/end-user/assign-billing-information-for-an-end-user crypto-as-a-service/openapi.json PUT /api/v1/end-users/{end_user_id}/billing/ This endpoint is a flexible tool designed to collect additional information required for ad-hoc workflows, such as billing or regulatory reporting. The flexibility of the endpoint allows the payload format to be customized according to the specific use case, as agreed upon with the partner. **Key aspects:** * **Negotiated Format**: The info field format must be negotiated with the client before usage to ensure it meets the requirements of the specific use case. * **Validation**: Validation rules for the info payload may vary depending on the format and purpose of the data being submitted. * **Versatility**: This endpoint is not limited to billing information but can be repurposed for other ad-hoc workflows requiring custom data exchange. * **Support for Data Corrections**: This endpoint can also be invoked to rectify previously submitted data. If multiple rectifications are sent, the system will always use the most recent rectification received. **Use Case Example: Regulatory Reporting in Argentina** A common use case for this endpoint is to collect information for regulatory reporting, such as meeting compliance requirements for Argentina's Financial Information Unit (UIF). In this scenario, the required format for the `info` field is as follows: Field Descriptions: - `condición_extranjero (boolean)`: Indicates if the person is a foreign resident (`true`) or not (`false`). - `cuit (string)`: Unique Tax Identification Code (Clave Única de Identificación Tributaria) of the person. - `apellidos (string)`: Last name(s) of the person. - `nombres (string)`: First name(s) of the person. - `tipo_documento (string)`: Type of document (DNI, PASAPORTE). - `num_documento (string)`: Document number. - `nacionalidad (string)`: Nationality of the person. - `dirección (string)`: Dirección/domicilio de la persona (ej: Soler 1501). - `provincia (string)`: Provincia del domicilio (ej: Buenos Aires). - `ciudad (string)`: Ciudad/localidad del domicilio (ej: La Plata). - `actividad (string)`: Actividad económica o condición fiscal de la persona (ej: Monotributista). - `fecha_nac (string)`: Date of birth in the format DD/MM/YYYY. - `fecha_alta (string)`: Registration date in the format `DD/MM/YYYY`. - `fecha_baja (string | null)`: Deregistration date in the format `DD/MM/YYYY` or `null` if not applicable. - `es_pep (boolean)`: Indicates if the person is a Politically Exposed Person (`true` or `false`). # Ban an End User Source: https://docs.ripio.com/crypto-as-a-service/end-user/ban-an-end-user crypto-as-a-service/openapi.json POST /api/v1/end-users/{end_user_id}/ban/ Endpoint that bans an End User After the ban operation is completed, the same information will be sent via the [IPN Webhook.](/crypto-as-a-service/ipn-webhooks/create-ipn-webhook) # Create End User Source: https://docs.ripio.com/crypto-as-a-service/end-user/create-end-user crypto-as-a-service/openapi.json POST /api/v1/end-users/ An End User represents the ultimate stakeholder that will be attached to all efective buy/sell trades. The partner will operate on-behalf-of the end user, serving as a middle-man for the quotation and trade execution process. In order to create an End User, the partner must provide an unique and arbitrary ID. All following trades will requiere this ID or REF in order to attach the trade data to that user. # List End Users Source: https://docs.ripio.com/crypto-as-a-service/end-user/list-end-users crypto-as-a-service/openapi.json GET /api/v1/end-users/ This endpoint allows to fetch a list of all active End Users already created. # Retrieve End User Source: https://docs.ripio.com/crypto-as-a-service/end-user/retrieve-end-user crypto-as-a-service/openapi.json GET /api/v1/end-users/{end_user_id}/ Retrieve end_user details given an end_user_id (external_ref). # Unban an End User Source: https://docs.ripio.com/crypto-as-a-service/end-user/unban-an-end-user crypto-as-a-service/openapi.json POST /api/v1/end-users/{end_user_id}/unban/ Endpoint that unbans an End User After the unban operation is completed, the same information will be sent via the [IPN Webhook.](/crypto-as-a-service/ipn-webhooks/create-ipn-webhook) # Error Handling Source: https://docs.ripio.com/crypto-as-a-service/errors/error-handling Error response format and the full list of B2B API error codes. If a request to B2B REST API fails, the response will contain an five-digit error code with a corresponding error message to indicate the details of the error. The following code snippet is a sample error response that contains an error code and message pair: ```json theme={null} { "code": 40001, "type": "NotAuthenticated", "detail": { "message": "Authentication credentials were not provided.", "code": "not_authenticated" }, "status": 403 } ``` ## Error Codes | Code | Error Category | Description | Resolution | | :- | :- | :- | :- | | 20000 | Validation Error | Raised when there is a semantic error in the payload of the request. | Check the docs for precise information on how to provide correct values for each resource atribute. | | 20001 | Parse Error | Raised when the API receives a malformed JSON document. | Check for syntax errors in the payload of your request. Also look for sample requests in the docs related to the desired operation. | | 20010 | Expired Quote | Raised when the received quote to execute has expired. | Generate a new quote and execute it within the 30 seconds time window. | | 20011 | Already Executed Quote | Raised when the received quote to execute has been executed before. | Try Generating a new quote with the former pair/amounts to replicate a similar trade. | | 20012 | Not Enough Balance | Raised when the amount to operate (sale/purchase) is larger than the balance amount of the debited currency. | Check end user's balances in order to inform the discrepancy before quote execution. Please contact our support team for further assistance. | | 20013 | Buy Limit Frequency Exceeded | Raised when the "BUY" frequency limit for a given end user has been reached. | Check end user's limits using Limits API in order to inform the discrepancy before performing a quote execution. | | 20014 | Sell Limit Frequency Exceeded | Raised when the "SELL" frequency limit for a given end user has been reached. | Check end user's limits using Limits API in order to inform the discrepancy before performing a quote execution. | | 20015 | Buy Limit Amount Exceeded | Raised when the "BUY" amount limit for a given end user has been reached. | Check end user's limits using Limits API in order to inform the discrepancy before performing a quote execution. | | 20016 | Sell Limit Amount Exceeded | Raised when the "SELL" amount limit for a given end user has been reached. | Check end user's limits using Limits API in order to inform the discrepancy before performing a quote execution. | | 20017 | Buy Transaction Min Limit Exceeded | Raised when the "BUY" amount min limit on a single transaction is exceeded. | Check Ripio's min and max transaction limits. | | 20018 | Buy Transaction Max Limit Exceeded | Raised when the "BUY" amount max limit on a single transaction is exceeded. | Check Ripio's min and max transaction limits. | | 20019 | Sell Transaction Min Limit Exceeded | Raised when the "SELL" amount min limit on a single transaction is exceeded. | Check Ripio's min and max transaction limits. | | 20020 | Sell Transaction Max Limit Exceeded | Raised when the "SELL" amount max limit on a single transaction is exceeded. | Check Ripio's min and max transaction limits. | | 20021 | Banned EndUser | Raised when a request comes from a banned user. | Please reach out our support team in order to get full detail on why the end user has been banned. | | 20022 | Swap Transaction Min Limit Exceeded | Raised when the "SWAP" amount min limit on a single transaction is exceeded. | Check Ripio's min and max transaction limits. | | 20023 | Swap Transaction Max Limit Exceeded | Raised when the "SWAP" amount max limit on a single transaction is exceeded. | Check Ripio's min and max transaction limits. | | 20024 | Swap Limit Frequency Exceeded | Raised when the "SWAP" frequency limit for a given end user has been reached. | Check end user's limits using Limits API in order to inform the discrepancy before performing a quote execution. | | 20025 | Swap Limit Amount Exceeded | Raised when the "SWAP" amount limit for a given end user has been reached. | Check end user's limits using Limits API in order to inform the discrepancy before performing a quote execution. | | 20026 | Quote With No Price | Raised when the received reusable quote to execute has no price. | Try Generating a new reusable quote with the former pair to replicate a similar trade. | | 20027 | Expired Withdrawal Fee | Raised when trying to execute a withdrawal fee that has already expired. | Request another withdrawal Fee. | | 20029 | Invalid address | Raised when the address provided is not valid on given network. | Try with a valid addres for that network. | | 20030 | Kyc not approved for end user | Raised when the end user doesn't have a kyc process approved. | Check the status of end user's verification submission | | 20031 | Kyc already approved for end user | Raised when the end user already has an approved kyc process. | - | | 20032 | Invalid or disabled payment method type | Raised when the payment\_method\_type does not exist or is disabled for the acccount. | Please reach out our support team. | | 20033 | Frequency limit exceeded for fiat withdrawals | Raised when the QTY limit for a given currency in a FIAT\_WITHDRAWAL operation is exceeded. | - | | 20034 | Amount limit exceeded for fiat withdrawals | Raised when the amount limit for a given currency in a FIAT\_WITHDRAWAL operation is exceeded. | - | | 20035 | Fiat account is required | Raised when a fiat account is required. | Create a Fiat account first. | | 20036 | Withdrawal amount less than network dust limit. | Raised when the amount of withdrawal request is less to the network dust limit. | Increase Withrawal amount. | | 20037 | There is no address created for end user and given network. | Raised when there is no address for a given concrete network name. | Create an AutoSwap address first. | | 20038 | The end user has no active opt-in | Raised when the given end user has no active OptIn, or none for the `asset` sent in an opt-out request. | Ensure the end user has an active opt-in for the product being opted out of. | | | | | | | 40000 | Authentication Failed | Raised when an incoming request includes incorrect authentication. | Check for invalid credentials in your request when trying to authenticate. | | 40001 | Not Authenticated | Raised when an unauthenticated request fails the permission checks. | Check if your request contains a valid and non-expired `access_token`. | | 40003 | Permission Denied | Raised when an authenticated request fails the permission checks. | Check if you have OAuth scopes to access requested resource. Please contact our support team if you think this is an error. | | 40004 | Not Found | Raised when a resource does not exists at the given URL. | Check if the resource you're looking for does exist. | | 40005 | Method Not Allowed | Raised when an incoming request has been performed with certain HTTP verb that is not valid for the requested resource. | Check the resource's docs section to learn the accepted HTTP methods. | | 40006 | Bad Request | Raised when historical market price API endpoint has not been used properly. | Check for syntax errors in the payload of your request. Also look for sample requests in the docs related to the desired operation. | | 40007 | Bad Request | Raised when a currency network is not allowed to operate the selected currency. | Check if your request contains a valid network for the selected currency. | | 40008 | Not Found | Raised when a currency doesn't have its limits configured. | Please reach out our support team with the error details and detailed execution flow. | | 40009 | Bad Request | Raised when an existing Quote with defined external\_ref has not been executed, thus it can not be excuted idempotently. | Use another external\_ref for the Quote. | | 40010 | Service Unavailable | Raised when there are no addresses available for networks compatible with a specific network name. | Please reach out our support team with the error details and detailed execution flow. | | 40015 | Unsupported Media Type | Raised when the content type of the request data is not JSON compliant. | Check if your request data is compliant with JSON format. | | 40029 | Throttled | Raised when an incoming request fails the throttling checks. | Check if you are exceding the daily/hourly request quota for your account. Please contact our support team if you think you'll need an extended quota for your use-case. | | | | | | | 50003 | Service Unavailable | Raised when the service (or requested resource) is not available. | Please reach out our support team with the error details and detailed execution flow. | # Create Fiat Account Source: https://docs.ripio.com/crypto-as-a-service/fiat-accounts/create-fiat-account crypto-as-a-service/openapi.json POST /api/v1/end-users/{end_user_id}/fiat-accounts/ This endpoint allows you to create a Fiat Account. A Fiat Account allows an end user to transfer FIAT money to ripio B2B environment. Such money will be used, later, to fulfill orders. While a process or flow may be technically feasible, it does not necessarily mean it is legally, financially, or operationally viable. These aspects must be reviewed and validated by the respective areas before implementation. The personal information that will be used to create the Fiat Account is the information that was uploaded in the KYC process. See: [Create KYC Submission](/crypto-as-a-service/kyc-submissions/create-kyc-submission). This endpoint behaves asynchronously, don't expect the Fiat Account to be created after the request execution. To check for its completion, use the [Retrieve Fiat Account endpoint](/crypto-as-a-service/fiat-accounts/retrieve-fiat-account). When the fiat account is created, a webhook will be sent. See [BaaS Webhook](/crypto-as-a-service/webhooks/baas-webhooks#fiat-account-creation-webhook) in the documentation. # List Fiat Account Source: https://docs.ripio.com/crypto-as-a-service/fiat-accounts/list-fiat-account crypto-as-a-service/openapi.json GET /api/v1/end-users/{end_user_id}/fiat-accounts/ This endpoint allows to fetch a list of the given user's fiat accounts. # Retrieve Fiat Account Source: https://docs.ripio.com/crypto-as-a-service/fiat-accounts/retrieve-fiat-account crypto-as-a-service/openapi.json GET /api/v1/end-users/{end_user_id}/fiat-accounts/{fiat_account_id}/ This endpoint allows to retrieve an end user's fiat accounts details. # List Fiat Deposits Source: https://docs.ripio.com/crypto-as-a-service/fiat-deposits/list-fiat-deposits crypto-as-a-service/openapi.json GET /api/v1/fiat-deposits/ Endpoint that list all the fiat deposits received. This endpoints allows to poll or reconcile fiat deposited transactions. We offer the capability to set a Webhook URL in order to be notified in real time. See [IPN Webhooks](/crypto-as-a-service/webhooks/baas-webhooks#ipn-fiat-deposit-webhook) in the documentation. # Retrieve Fiat Deposit Source: https://docs.ripio.com/crypto-as-a-service/fiat-deposits/retrieve-fiat-deposit crypto-as-a-service/openapi.json GET /api/v1/fiat-deposits/{fiat_deposit_id}/ Endpoint that retrieves details of a received fiat deposit by its id. We offer the capability to set a Webhook URL in order to be notified in real time. See [IPN Webhooks](/crypto-as-a-service/webhooks/baas-webhooks#ipn-fiat-deposit-webhook) in the documentation. # Create Fiat Withdrawal Source: https://docs.ripio.com/crypto-as-a-service/fiat-withdrawals/create-fiat-withdrawal crypto-as-a-service/openapi.json POST /api/v1/fiat-withdrawals/ This endpoint allows the creation of a Fiat withdrawal. It will debit from the end user's currency's balance and withdraw such amount to the specified [Beneficiary Fiat Account](/crypto-as-a-service/beneficiary-fiat-accounts/create-beneficiary-fiat-account). This endpoint behaves asynchronously, don't expect the withdrawal to be completed after the request execution. To check for its completion, use the [Retrieve Fiat Withdrawal endpoint](/crypto-as-a-service/fiat-withdrawals/retrieve-fiat-withdrawal). Updates for Fiat Withdrawals will be communicated via IPN webhooks. See [IPN Webhooks](/crypto-as-a-service/webhooks/baas-webhooks#ipn-fiat-withdrawal-webhook) in the documentation. # List Fiat Withdrawals Source: https://docs.ripio.com/crypto-as-a-service/fiat-withdrawals/list-fiat-withdrawals crypto-as-a-service/openapi.json GET /api/v1/fiat-withdrawals/ Endpoint that lists every fiat withdrawal operation. # Retrieve Fiat Withdrawal Source: https://docs.ripio.com/crypto-as-a-service/fiat-withdrawals/retrieve-fiat-withdrawal crypto-as-a-service/openapi.json GET /api/v1/fiat-withdrawals/{fiat_withdrawal_id}/ Endpoint that retrieves an specific Fiat Withdrawal given a fiat_withdrawal_id. We offer the capability to set a Webhook URL in order to be notified in real time. Please note that you should expect a payload format similar to the one below. See [IPN Webhooks](/crypto-as-a-service/webhooks/baas-webhooks#ipn-fiat-withdrawal-webhook) in the documentation. # List IPN Messages Source: https://docs.ripio.com/crypto-as-a-service/ipn-messages/list-ipn-messages crypto-as-a-service/openapi.json GET /api/v1/ipn/messages/ List the webhook messages issued for your account. Each item of `results` is the message exactly as it was delivered to your webhook, with the same `message_id`, `event_type`, `issue_datetime` and `payload` you received. The `message_id` is what tells a message you already processed apart from one you did not. Use it to look up a notification you did not process, or to reconcile what was sent against what you have on your side. Messages are returned newest first, and every filter can be combined. The result is always restricted to your own account. Messages issued before this endpoint was released carry no creation date and no recorded operation: they are listed, but the `date_gte`, `date_lte` and `operation_type` filters leave them out. # Retrieve IPN Message Source: https://docs.ripio.com/crypto-as-a-service/ipn-messages/retrieve-ipn-message crypto-as-a-service/openapi.json GET /api/v1/ipn/messages/{message_id}/ Retrieve a single message by the `message_id` it carries in its body. The response is the message exactly as it was delivered to your webhook, the same one the list returns for it. Messages issued before this endpoint was released have no `message_id` and are only reachable through the list. # Create IPN Webhook Source: https://docs.ripio.com/crypto-as-a-service/ipn-webhooks/create-ipn-webhook crypto-as-a-service/openapi.json POST /api/v1/ipn/webhooks/ Endpoint that performs the creation of an IPN Webhook for the current account. **Important**: This endpoint is only available in Sandbox. # List Available IPN Webhooks Source: https://docs.ripio.com/crypto-as-a-service/ipn-webhooks/list-available-ipn-webhooks crypto-as-a-service/openapi.json GET /api/v1/ipn/webhooks/ Endpoint that list all the available IPN Webhooks for the current account. **Important**: This endpoint is only available in Sandbox. # Retrieve IPN Webhooks Source: https://docs.ripio.com/crypto-as-a-service/ipn-webhooks/retrieve-ipn-webhooks crypto-as-a-service/openapi.json GET /api/v1/ipn/webhooks/{ipn_webhook_id}/ Endpoint that allows the retrieval of a given IPN Webhooks. **Important**: This endpoint is only available in Sandbox. # Update IPN Webhook Source: https://docs.ripio.com/crypto-as-a-service/ipn-webhooks/update-ipn-webhook crypto-as-a-service/openapi.json PUT /api/v1/ipn/webhooks/{ipn_webhook_id}/ Endpoint that performs an update of an IPN Webhook for the current account. **Important**: This endpoint is only available in Sandbox. # KYC Requirements Source: https://docs.ripio.com/crypto-as-a-service/kyc-requirements/kyc-requirements crypto-as-a-service/openapi.json GET /api/v1/kyc-requirements/ This endpoint returns a list of fields that needs to be provided on a KYC submission. The response is structured in a list of fields so that an UI can be constructed from it. Each field has the following structure: ```json theme={null} { "field_name": string, "required": boolean, "type": string (TEXT, BOOLEAN, DATE, NUMERIC, CHOICE), "choices": array, "value": label, "label": string } ``` Notes: * `id_number` is DNI for Argentina. * `registered_tax_payer` in Argentina is "Responsable Inscripto". # Create KYC Submission Source: https://docs.ripio.com/crypto-as-a-service/kyc-submissions/create-kyc-submission crypto-as-a-service/openapi.json POST /api/v1/end-users/{end_user_id}/kyc This endpoint allows you to submit user's kyc information. This endpoint behaves asynchronously, don't expect the KYC Submission to be approved after the request execution. To check for its completion, use the [Retrieve KYC Submission endpoint](/crypto-as-a-service/kyc-submissions/retrieve-kyc-submission). When the verification submission is completed, a webhook will be sent. See [BaaS Webhooks](/crypto-as-a-service/webhooks/baas-webhooks#completed-verification-submission-webhook) in the documentation. # List KYC Submissions Source: https://docs.ripio.com/crypto-as-a-service/kyc-submissions/list-kyc-submissions crypto-as-a-service/openapi.json GET /api/v1/end-users/{end_user_id}/kyc Endpoint that list an end user's KYC Submissions # Retrieve KYC Submission Source: https://docs.ripio.com/crypto-as-a-service/kyc-submissions/retrieve-kyc-submission crypto-as-a-service/openapi.json GET /api/v1/end-users/{end_user_id}/kyc/{submission_id}/ Endpoint that retrieves an specific KYC submission given a submission ID. We offer the capability to set a Webhook URL in order to be notified in real time. Please note that you should expect a payload format similar to the one below. See [BaaS Webhooks](/crypto-as-a-service/webhooks/baas-webhooks#completed-verification-submission-webhook) in the documentation. # List Opt Ins Source: https://docs.ripio.com/crypto-as-a-service/opt-ins/list-opt-ins crypto-as-a-service/openapi.json GET /api/v1/opt-in/ Retrieve a list of all active and historical opt-in records for a given user, indicating whether the user's balances are enrolled to earn yield. Supports filtering by user ID, asset type, opt-in status, and creation date range. # Retrieve Opt In Source: https://docs.ripio.com/crypto-as-a-service/opt-ins/retrieve-opt-in crypto-as-a-service/openapi.json GET /api/v1/opt-in/{opt_in_id}/ Fetch detailed information about a specific opt-in entry using its unique identifier. # Yield Opt In Source: https://docs.ripio.com/crypto-as-a-service/opt-ins/yield-opt-in crypto-as-a-service/openapi.json POST /api/v1/opt-in/ Register a user to participate in the yield-earning program. This enables daily interest accrual on supported assets held by the user. By default the user is opted into every product enabled for your account, and that form of the request requires the user to have no active opt-in at all. Send the optional `asset` field to opt into a single product instead, one request per product, leaving the other opts of the user untouched. Opt-in applies to the full balance of each opted-in asset. The response is always a list, with one element per product opted into. Sending `asset` therefore returns a list of a single element. The request and response example selectors above are independent of each other. Pick the same name in both — `Opt in to every enabled product` or `Opt in to a single product` — to see a request paired with the response it actually produces. ## Opt in to every enabled product Omit `asset`. Every product enabled for your account is opted into, and one opt-in comes back per product. This form requires the user to have **no active opt-in at all**. Omitting `asset` is not a top-up. It does not add the products a user is missing: if the user already has an active opt-in — even for a single product — the request fails with `400` and nothing changes. A user opted into `USDT` only, on an account where `BTC`, `ETH` and `USDT` are enabled: ```json Request theme={null} { "end_user_id": "75c741a2-c3c8-46b1-a807-4fa1f40bf048" } ``` ```json Response theme={null} { "code": 20000, "type": "ValidationError", "detail": { "non_field_errors": [ { "message": "An active OptIn already exists for user 75c741a2-c3c8-46b1-a807-4fa1f40bf048", "code": "invalid" } ] }, "status": 400 } ``` The user stays opted into `USDT` alone. To add `BTC` and `ETH`, send one request per product with `asset`, as shown below. ```json Request theme={null} { "end_user_id": "75c741a2-c3c8-46b1-a807-4fa1f40bf048" } ``` ```json Response theme={null} [ { "id": "c468be09-3a1f-41aa-ad08-c7d715957105", "transaction_id": "e826023d-fddc-4ce1-bdac-7506ed17eb0e", "end_user_id": "75c741a2-c3c8-46b1-a807-4fa1f40bf048", "asset": "BTC", "created_at": "2025-04-10T15:53:11.847992Z", "opt_out_at": null, "is_active": true }, { "id": "c468be09-3a1f-41aa-ad08-c7d715957106", "transaction_id": "e826023d-fddc-4ce1-bdac-7506ed17eb01", "end_user_id": "75c741a2-c3c8-46b1-a807-4fa1f40bf048", "asset": "ETH", "created_at": "2025-04-10T15:53:11.847993Z", "opt_out_at": null, "is_active": true } ] ``` ## Opt in to a single product Send `asset`. Only that product is opted into, and the other opts of the user are left as they are. Send one request per product you want to enable. This is also the only way to enable a further product for a user who is already opted into something: one request per product to add, each naming its `asset`. ```json Request theme={null} { "end_user_id": "75c741a2-c3c8-46b1-a807-4fa1f40bf048", "asset": "USDT" } ``` ```json Response theme={null} [ { "id": "c468be09-3a1f-41aa-ad08-c7d715957107", "transaction_id": "e826023d-fddc-4ce1-bdac-7506ed17eb02", "end_user_id": "75c741a2-c3c8-46b1-a807-4fa1f40bf048", "asset": "USDT", "created_at": "2025-04-10T15:53:11.847994Z", "opt_out_at": null, "is_active": true } ] ``` # List Opt Outs Source: https://docs.ripio.com/crypto-as-a-service/opt-outs/list-opt-outs crypto-as-a-service/openapi.json GET /api/v1/opt-out/ Retrieve a list of opt-out events for users who have exited the yield-earning program. Each entry links to the original opt-in. Supports filtering by user ID, asset, and creation date range. # Retrieve Opt Out Source: https://docs.ripio.com/crypto-as-a-service/opt-outs/retrieve-opt-out crypto-as-a-service/openapi.json GET /api/v1/opt-out/{opt_out_id}/ Retrieve details of a specific opt-out record, including the associated opt-in and transaction information, by providing its unique ID. # Yield Opt Out Source: https://docs.ripio.com/crypto-as-a-service/opt-outs/yield-opt-out crypto-as-a-service/openapi.json POST /api/v1/opt-out/ De-register a user from the yield-earning program. The user will stop receiving interest on their eligible balances. By default every active opt-in of the user is closed. Send the optional `asset` field to opt out of a single product instead, leaving the other opts of the user active. Requires that the user has an active opt-in for what is being opted out of. The response is always a list, with one element per product opted out of. Sending `asset` therefore returns a list of a single element. The request and response example selectors above are independent of each other. Pick the same name in both — `Opt out of every active opt-in` or `Opt out of a single product` — to see a request paired with the response it actually produces. ## Opt out of every active opt-in Omit `asset`. Every active opt-in of the user is closed, and one opt-out comes back per product. ```json Request theme={null} { "end_user_id": "75c741a2-c3c8-46b1-a807-4fa1f40bf048" } ``` ```json Response theme={null} [ { "id": "c56eb572-9435-4643-91aa-337c7058bc86", "transaction_id": "da0cbef0-4cb1-40e3-bdd1-65247739a5e5", "end_user_id": "75c741a2-c3c8-46b1-a807-4fa1f40bf048", "opt_in_id": "c468be09-3a1f-41aa-ad08-c7d715957105", "asset": "BTC", "created_at": "2025-04-10T15:57:28.780194Z" }, { "id": "c56eb572-9435-4643-91aa-337c7058bc87", "transaction_id": "da0cbef0-4cb1-40e3-bdd1-65247739a5e6", "end_user_id": "75c741a2-c3c8-46b1-a807-4fa1f40bf048", "opt_in_id": "c468be09-3a1f-41aa-ad08-c7d715957106", "asset": "ETH", "created_at": "2025-04-10T15:57:28.780195Z" } ] ``` ## Opt out of a single product Send `asset`. Only the opt-in of that product is closed and the rest stay active. The user must have an active opt-in for it, otherwise the request fails with error code `20038` and nothing is changed. ```json Request theme={null} { "end_user_id": "75c741a2-c3c8-46b1-a807-4fa1f40bf048", "asset": "USDT" } ``` ```json Response theme={null} [ { "id": "c56eb572-9435-4643-91aa-337c7058bc88", "transaction_id": "da0cbef0-4cb1-40e3-bdd1-65247739a5e7", "end_user_id": "75c741a2-c3c8-46b1-a807-4fa1f40bf048", "opt_in_id": "c468be09-3a1f-41aa-ad08-c7d715957107", "asset": "USDT", "created_at": "2025-04-10T15:57:28.780196Z" } ] ``` # Choose your integration Source: https://docs.ripio.com/crypto-as-a-service/overview/choose-your-integration Decide between the CaaS API and the embeddable Widget to add crypto trading to your platform. There are two ways to add crypto trading — buying, selling, deposits, withdrawals — to your platform. Both run on the same account and the same underlying service — they differ in how much of the user experience you build yourself. ## The two paths Call the REST API directly and build your own UI. Maximum control over the experience. Embed Ripio's pre-built, themeable UI. Fastest way to ship. ## Which one should you use? | Feature | API integration | Widget integration | | - | - | - | | **Integration type** | Direct API calls, backend-to-backend primarily | Embeddable UI, frontend integration | | **User experience** | Fully customizable by the partner | Pre-built UI, themeable via CSS custom properties | | **Development effort** | Higher — you build the UI and handle trading logic yourself | Lower — mainly generating session codes and mounting the component | | **Control** | Granular control over quotes, execution and every screen | Streamlined flow managed by the widget | | **Use case** | Deep, custom trading integrations | A quick, easy way to add buy/sell | | **Authentication** | OAuth2 Client Credentials for API access | One-time session code generated via API, which the widget exchanges for its session | **Choose the API** if you want to own the end-to-end experience and embed trading natively in your product. **Choose the Widget** if you want to go live quickly with minimal development. ## Next steps Once you've decided: * **API** → [Get an access token](/crypto-as-a-service/access-token/get-access-token), then create an [end user](/crypto-as-a-service/end-user/create-end-user) and a [reusable quote](/crypto-as-a-service/reusable-quotes/create-reusable-quote). * **Widget** → [Widget integration](/crypto-as-a-service/widget/overview). # Crypto as a Service Source: https://docs.ripio.com/crypto-as-a-service/overview/introduction A brief introduction to B2B API. B2B API allows a third party to access different use cases and services inside of Ripio ecosystem. It is organized around RESTful constraints, with requests and responses in JSON format, using HTTP in the standard format and the answer codes. The API is designed to be compliant with OpenAPI Specification v3.0, which is a trusted open source standard for HTTP-Based APIs. The API offers the ability to test it with a simple, yet powerful sandbox that will enhance developer experience and replicate production capabilities in a well isolated and safe environment, giving developers full freedom to try the integration without compromises. You can integrate by calling the API directly, or by embedding Ripio's pre-built [Widget](/crypto-as-a-service/widget/overview). See [Choose your integration](/crypto-as-a-service/overview/choose-your-integration) for a full comparison. # Get Account Pairs Source: https://docs.ripio.com/crypto-as-a-service/pairs/get-account-pairs crypto-as-a-service/openapi.json GET /api/v1/pairs/ Returns all enabled pairs for the Account. A currency pair is a quotation for two different currencies. It is the amount you would pay in one currency for a unit of another currency. For instance, when a trader is quoted USDT/ARS 110.50 it means that the trader can exchange 1 USDT and receive 110.50 ARS. # Get Limits for End User. Source: https://docs.ripio.com/crypto-as-a-service/periodic-limits/get-limits-for-end-user crypto-as-a-service/openapi.json GET /api/v1/end-users/{end_user_id}/limits/ Limits are set per operation basis, so one should expect for "BUY", "SELL" or "SWAP". All End Users have certain restrictions when executing trades. Limits model seeks to control the maximun allowed frequency and magnitude of the volume operated by an end user in certain period: - **Frequency Limits**: Indicates which is the daily / monthly / annual limit of trades that an end user can carry out. A reset of these limits occurs at the end of each period. - **Tradable Amount**: Indicates which is the maximum operable amount for a purchase or sale trade within a time period, stratified by day / month / year. A reset of these limits occurs at the end of each period. Currently, the limits model applies to two separate types of operations: **Buy Trades** and **Sell Trades**. Each type of trade maintains its limits separately, indicating the **Maximum Frequency** and **Tradable Amount** limits for each one, stratified by day / month / year. It is important to note that each limit is reset at the end of each period. For instance take in consideration the following scenario: Given that an end user can perform at most 6 BUY trades a day, and the End User _CUST-1567_ has already performed 6 BUY trades this day, When the end user _CUST-1567_ tries to perform another trade, the system will respond indicating it has excedeed the daily limit set to 6 trades a day. Of course, the following day, end user _CUST-1567_ will have 0 trades performed, due to the daily reset, thus it will be able to perform another 6 trades. It's important to note that Limits are set on commercial and compliance aggreements basis, please reach out your commercial partner if you want to know specific details about limits for your use case. # Get Last Market Rates Source: https://docs.ripio.com/crypto-as-a-service/rates/get-last-market-rates crypto-as-a-service/openapi.json GET /api/v1/rates/ An exchange rate is the rate at which one currency will be exchanged for another currency. Returns last market rates (BUY and SELL) for each account pair enabled. # Execute Reusable Quote Source: https://docs.ripio.com/crypto-as-a-service/reusable-quote-execution/execute-reusable-quote crypto-as-a-service/openapi.json POST /api/v1/reusable-quotes/{reusable_quote_id}/actions/execute/ Reusable Quote Executions buy or sell assets using a Reusable Quote obtained from the [Reusable Quote Creation](#operations/createReusableQuoteResponse) flow. The asset, and guaranteed price/rate of the execution are specified by the quote with ID `quote_id`. The amount to buy or sell must be specified in either fiat or crypto by setting exactly one of: * `base_amount` to specify the amount of crypto to buy or sell. * `quote_amount` to specify the amount of fiat to spend or acquire. An otherwise-valid request to create a quote execution may fail with the following types of errors: * **Expired** if the reusable quote with ID `reusable_quote_id` has expired. * **Insufficient Funds** if the end user with ID `end_user_id` has insufficient available balance to fund the execution. * **Quote with no price** if a Reusable Quote has its prices set to null and status UNA_PRICE (Unavailable Price). * **Buy Limit Frequency Exceeded** if the end_user reached a Daily/Monthly/Yearly frequency limit for BUY Trades. * **Sell Limit Frequency Exceeded** if the end_user reached a Daily/Monthly/Yearly frequency limit for SELL Trades. * **Buy Limit Amount Exceeded** if the end_user reached a Daily/Monthly/Yearly amount limit for BUY Trades. * **Sell Limit Amount Exceeded** if the end_user reached a Daily/Monthly/Yearly amount limit for SELL Trades. # Create Reusable Quote Source: https://docs.ripio.com/crypto-as-a-service/reusable-quotes/create-reusable-quote crypto-as-a-service/openapi.json POST /api/v1/reusable-quotes/ ReusableQuotes are "held rates" offered by Ripio to buy or sell assets at a specific price within a period of time - for example, the option to buy ETH within the next 30 seconds for $10,000. The typical Quotes workflow is as follows: * Call [List Market Rates](#operation/listRatesViewSets) to get the latest available quoted prices for the assets you want to buy or sell. Please note that these rates are subject to change depending on the amount of base/quote currency the end user is willing to trade. * Partner performs a POST request to [ReusableQuote Endpoint](#operation/createReusableQuoteResponse), B2B will respond a price for a price valid for 30 second. * Present the offered price to the end-user, with a timer indicating the time to expiration (30 seconds). * If a user accepts the price, call [Create Reusable Quote Execution](#operation/executeReusableQuoteResponse) passing the reusable quote id, end user external ref, operation type and base or quote amount. * Call [Get Transaction](#operation/retrieveTransaction) or [Get Transaction List](#operation/listTransactions) to query trade status information. # List Account Reusable Quotes Source: https://docs.ripio.com/crypto-as-a-service/reusable-quotes/list-account-reusable-quotes crypto-as-a-service/openapi.json GET /api/v1/reusable-quotes/ Retrieve a paginated list of generated reusable quotes. # Retrieve Account Reusable Quote Source: https://docs.ripio.com/crypto-as-a-service/reusable-quotes/retrieve-account-reusable-quote crypto-as-a-service/openapi.json GET /api/v1/reusable-quotes/{reusable_quote_id}/ Retrieve previously generated reusable quote given an id. # Simulate Fiat Deposit Source: https://docs.ripio.com/crypto-as-a-service/simulate-fiat-deposit/simulate-fiat-deposit crypto-as-a-service/openapi.json POST /api/v1/simulate-fiat-deposit Endpoint that simulates a fiat deposit in the fiat account with the cvu passed in the body of the request. # Execute Swap Reusable Quote Source: https://docs.ripio.com/crypto-as-a-service/swap-reusable-quote-execution/execute-swap-reusable-quote crypto-as-a-service/openapi.json POST /api/v1/swap-quotes/{swap_quote_id}/actions/execute/ Swap Quote Executions performs assets using a Reusable Quote obtained from the [Swap Reusable Quote Creation](#operations/createSwapQuote) flow. The asset, and guaranteed price/rate of the execution are specified by the quote with ID `quote_id`. The amount to swap must be specified by setting the following attribute: * `base_amount` to specify the amount of crypto that will be debited from base asset in order to accredit the equivalent quote asset. An otherwise-valid request to create a quote execution may fail with the following types of errors: * **Expired** if the reusable quote with ID `swap_quote_id` has expired. * **Insufficient Funds** if the end user with ID `end_user_id` has insufficient available balance to fund the execution. * **Swap Limit Frequency Exceeded** if the end_user reached a Daily/Monthly/Yearly frequency limit for SWAP Trades. * **Swap Limit Amount Exceeded** if the end_user reached a Daily/Monthly/Yearly amount limit for SWAP Trades. # Create Swap Reusable Quotes Source: https://docs.ripio.com/crypto-as-a-service/swap-reusable-quotes/create-swap-reusable-quotes crypto-as-a-service/openapi.json POST /api/v1/swap-quotes/ SwapReusableQuotes are "held rates" offered by Ripio to swap assets at a specific price within a period of time. The typical Quotes workflow is as follows: * Partner performs a POST request to [Swap Reusable Quote Endpoint](#operation/createSwapQuote), B2B will respond a price for a price valid for 30 second. * Present the offered price to the end-user, with a timer indicating the time to expiration (30 seconds). * If a user accepts the price, call [Create Swap Reusable Quote Execution](#operation/executeSwapQuote) passing the reusable quote id, end user external ref, operation type and base or quote amount. * Call [Get Transaction](#operation/retrieveTransaction) or [Get Transaction List](#operation/listTransactions) to query trade status information. # List Account Swap Quotes Source: https://docs.ripio.com/crypto-as-a-service/swap-reusable-quotes/list-account-swap-quotes crypto-as-a-service/openapi.json GET /api/v1/swap-quotes/ Retrieve a paginated list of generated swap quotes. # List Transactions Source: https://docs.ripio.com/crypto-as-a-service/transactions/list-transactions crypto-as-a-service/openapi.json GET /api/v1/transactions/ List all executed transactions. # Retrieve Transaction Source: https://docs.ripio.com/crypto-as-a-service/transactions/retrieve-transaction crypto-as-a-service/openapi.json GET /api/v1/transactions/{txn_id}/ Retrieve transaction details given an id. # Get Transaction Limits Source: https://docs.ripio.com/crypto-as-a-service/txn-limits/get-transaction-limits crypto-as-a-service/openapi.json GET /api/v1/transactions/limits/ A transaction limit is the current maximum or minimum amount allowed for a single operation (BUY, SELL, SWAP) and a given fiat currency. # BaaS Webhooks Source: https://docs.ripio.com/crypto-as-a-service/webhooks/baas-webhooks Documentation of Banking as a Service Webhooks The B2B API offers a webhook mechanism to notify about events that occur asynchronously. For Banking as a Service (BaaS), there are two types of notifications sent via webhooks: event notifications and IPN (Instant Payment Notification) webhooks. Event notifications inform about specific actions such as verification completions or account creations, while IPN webhooks notify about transaction events like deposits and withdrawals. ### Completed Verification Submission Webhook For the KYC flow, a notification will be sent once the verification process has been completed. The payload will be as follows: ```json theme={null} { "event_type": "KYC.COMPLETED", "issued_datetime": "2025-02-14T15:11:47.910383Z", "payload": { "submission_id": "ae16bd7d-f065-434e-876c-192c373d38d1", "provider_url": null, "status": "COMPLETED", "created_at": "2025-02-14T14:59:41.156735Z" } } ``` | Key | Type | Description | | - | - | - | | `event_type` | *string* | Indicate which type of event the currently sent webhook corresponds to, in this case `KYC.COMPLETED` | | `issued_datetime` | *string* | Indicate the datetime the webhook was sent. | | `payload` | *string* | Payload with the information about the completed verification submission. | | `submission_id` | *string* | KYC Submission unique identifier. | | `provider_url` | *string or null* | URL to redirect users in order for them upload or fulfill extra information. E.g: ID photo, proof of life, etc. May be null if no further information is required. | | `status` | *string* | KYC Submission status. | | `created_at` | *string* | ISO datetime indicating Verification Submission creation date. | ### Fiat Account Creation Webhook In the Fiat Account creation flow, a notification will be sent once the Fiat Account creation has been completed. The payload will be as follows: ```json theme={null} { "event_type": "FIAT_ACCOUNT.ENABLED", "issued_datetime": "2025-02-14T15:11:47.910383Z", "payload": { "fiat_account_id": "0816ecf8-4ebc-4abb-a273-ab70bc992d1e", "created_at": "2025-02-14T15:11:47.910383Z", "payment_method_type": "bank_transfer", "currency": "ARS", "status": "ENABLED", "fiat_payment_instructions": { "cvu": "0000465160000000100092" }, "end_user_id": "customer_10" } } ``` | Key | Type | Description | | - | - | - | | `event_type` | *string* | Indicate which type of event the currently sent webhook corresponds to, in this case `FIAT_ACCOUNT.ENABLED` | | `issued_datetime` | *string* | Indicate the datetime the webhook was sent. | | `payload` | *string* | Payload with the information about the enabled fiat account. | | `fiat_account_id` | *string* | UUID4 formated unique id. | | `payment_method_type` | *string* | It indicates the account type. Usually `bank_transfer`. | | `currency` | *string* | It defines the currency that the FiatAccount accepts. | | `status` | *string* | Indicates the Fiat Account status. Only when "ENABLED", the fiat payment instructions will be provided. | | `fiat_payment_instructions` | *string* | JSON fields with account identification details. It varies based on the account type. | | `cvu` | *string* | Accunt identifier number for AR Fiat Accounts. | | `created_at` | *string* | ISO datetime indicating Fiat Account creation date. | | `end_user_id` | *string* | End user's external ref | ### Beneficiary Fiat Account Creation Webhook When a beneficiary fiat account is enabled, a notification will be sent with the following structure: ```json theme={null} { "event_type": "BENEFICIARY_FIAT_ACCOUNT.ENABLED", "issue_datetime": "2025-06-04T12:42:26.061Z", "payload": { "status": "ENABLED", "currency": "ARS", "created_at": "2025-06-04T12:42:25.816728Z", "end_user_id": "customer_10", "payment_method_type": "bank_transfer", "fiat_payment_instructions": { "cvu": "0000465160000000100092" }, "beneficiary_fiat_account_id": "e7e6178a-eda7-4217-bcde-a06a1b1670fa" } } ``` | Key | Type | Description | | - | - | - | | `event_type` | *string* | Indicate which type of event the currently sent webhook corresponds to, in this case `BENEFICIARY_FIAT_ACCOUNT.ENABLED` | | `issue_datetime` | *string* | Indicate the datetime the webhook was sent. | | `payload` | *string* | Payload with the information about the enabled beneficiary fiat account. | | `beneficiary_fiat_account_id` | *string* | UUID4 formated unique id. | | `payment_method_type` | *string* | It indicates the account type. Usually `bank_transfer`. | | `currency` | *string* | It defines the currency that the Beneficiary Fiat Account accepts. | | `status` | *string* | Indicates the Beneficiary Fiat Account status. Only when "ENABLED", the fiat payment instructions will be provided. | | `fiat_payment_instructions` | *string* | JSON fields with account identification details. It varies based on the account type. | | `cvu` | *string* | Account identifier number for AR Beneficiary Fiat Accounts. | | `created_at` | *string* | ISO datetime indicating Beneficiary Fiat Account creation date. | | `end_user_id` | *string* | End user's external ref | ### IPN Fiat Deposit Webhook When a Fiat Deposit is credited to a specific Fiat Account, an IPN will be sent with the following structure: ```json theme={null} { "message_id": "9d1f3b57-6a8c-4e0c-b4df-0f2e4a6c8b9d", "event_type": "FIAT_DEPOSIT.COMPLETED", "issued_datetime": "2025-02-14T15:11:47.910383Z", "payload": { "id": "8142b065-79c4-4f48-9e33-11b17bc658d4", "created_at": "2024-04-23T14:48:33.895551Z", "updated_at": "2024-04-23T14:49:25.342111Z", "currency_code": "ARS", "end_user_id": "customer_10", "amount": "12345.67", "status": "COMPLETED", "refunded": false, "refund_fiat_withdrawal_id": null, "origin_account_identifier": "0000465160000000100092", "origin_account_holder_id": "20388003629", "origin_account_holder_name": "Carlos Pineda" } } ``` | Key | Type | Description | | | - | - | - | - | | `message_id` | *string* | Unique identifier of this message. The same message delivered again on a retry, or read from the [List IPN Messages endpoint](/crypto-as-a-service/ipn-messages/list-ipn-messages), carries this same value: use it to discard duplicates. | | | `event_type` | *string* | Indicate which type of event the currently sent webhook corresponds to, in this case `FIAT_DEPOSIT.COMPLETED` | | | `issued_datetime` | *string* | Indicate the datetime the webhook was sent. | | | `payload` | *string* | Payload with the information about the completed Fiat Deposit. | | | `id` | *string* | Unique ID for this Fiat Deposit. | | | `created_at` | *string* | Indicates the creation date of the Fiat Deposit. | | | `updated_at` | *string* | Indicates the update date of the Fiat Deposit. | | | `currency_code` | *string* | Indicates the currency that has been deposited. | | | `end_user_id` | *string* | End user's external ref, usually a uuid. Indicates the end user that has received the Fiat Deposit. | | | `amount` | *string* | Indicates the amount deposited. | | | `status` | *string* | Indicates Fiat Deposit status. | | | `refunded` | *boolean* | Indicates if the fiat deposit was refunded. | | | `refund_fiat_withdrawal_id` | *string or null* | Indicates the Fiat Withdrawal id associated with the refund, could be null. | | | `origin_account_identifier` | *string* | Indicates the identifier of the fiat deposit's issuing account. | | | `origin_account_holder_id` | *string* | Indicates the identification of the fiat deposit's issuing account holder. | | | `origin_account_holder_name` | *string* | Indicates the name of the fiat deposit's issuing account holder. | | If a Fiat Deposit is blocked due to limits, an IPN will be sent with the `event_type` value equal to: `FIAT_DEPOSIT.BLOCKED_LIMIT_EXCEEDED`. If a Fiat Deposit is refunded, an IPN will be sent with the following information: ```json theme={null} { "message_id": "a0e2c468-7b9d-4f1d-c5ea-1a3f5b7d9c0e", "event_type": "FIAT_DEPOSIT.CANCELLED", "issued_datetime": "2025-02-14T15:11:47.910383Z", "payload": { "id": "8142b065-79c4-4f48-9e33-11b17bc658d4", "created_at": "2024-04-23T14:48:33.895551Z", "updated_at": "2024-04-23T14:49:25.342111Z", "currency_code": "ARS", "end_user_id": "customer_10", "amount": "12345.67", "status": "CANCELLED", "refunded": true, "refund_fiat_withdrawal_id": "6f907606-14f8-421c-80e2-e9c2c777aeec", "origin_account_identifier": "0000465160000000100092", "origin_account_holder_id": "20388003629", "origin_account_holder_name": "Carlos Pineda" } } ``` ### IPN Fiat Withdrawal Webhook When a Fiat Whitdrawal is crcompleted, an IPN will be sent with the following structure: ```json theme={null} { "message_id": "b1f3d579-8c0e-4a2e-d6fb-2b4a6c8e0d1f", "event_type": "FIAT_WITHDRAWAL.COMPLETED", "issued_datetime": "2025-02-14T15:11:47.910383Z", "payload": { "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "transaction_id": "0fec1e58-b197-4052-99cf-2218496c5482", "created_at": "2019-08-24T14:15:22Z", "updated_at": "2019-08-24T14:18:22Z", "end_user_id": "customer_10", "amount": "12345.67", "status": "COMPLETED", "external_ref": "ac243840-ac04-4b3a-9efb-176554d4a029", "beneficiary_fiat_account_id": "4123e456-e89b-12d3-a456-4bedc4266abc", "is_fiat_deposit_refund": false, "refunded_fiat_deposit": null } } ``` | Key | Type | Description | | | - | - | - | - | | `message_id` | *string* | Unique identifier of this message. The same message delivered again on a retry, or read from the [List IPN Messages endpoint](/crypto-as-a-service/ipn-messages/list-ipn-messages), carries this same value: use it to discard duplicates. | | | `event_type` | *string* | Indicate which type of event the currently sent webhook corresponds to, in this case `FIAT_WITHDRAWAL.COMPLETED` | | | `issued_datetime` | *string* | Indicate the datetime the webhook was sent. | | | `payload` | *string* | Payload with the information about the completed Fiat Withdrawal. | | | `id` | *string* | UUID used as Fiat Withdrawal identifier. | | | `created_at` | *string* | Indicates the creation date of the Fiat Withdrawal. | | | `updated_at` | *string* | Indicates the update date of the Fiat Withdrawal. | | | `currency_code` | *string* | Indicates the asset name involved in the Fiat Withdrawal. | | | `end_user_id` | *string* | End user's external ref, usually a uuid. Identifies the user that owns this operation. | | | `amount` | *string* | Indicates the fiat amount withdrawed. | | | `status` | *string* | Specifies the Fiat Withdrawal status. | | | `external_ref` | *string* | Client side identification reference. Usually a UUID. | | | `beneficiary_fiat_account_id` | *string or null* | Indicates the Fiat Withdrawal id associated with the refund, could be null. | | | `is_fiat_deposit_refund` | *boolean* | Indicates the identifier of the fiat deposit's issuing account. | | | `refunded_fiat_deposit` | *string* | Indicates the identification of the fiat deposit's issuing account holder. | | If a Fiat Withdrawal is not completed due to an external issue, it will remain in a `CANCELLED` state, and an IPN will be sent with `event_type` equal to: `FIAT_WITHDRAWAL.CANCELLED`. When a Fiat Withdrawal is created, it will be in a `PENDING` status, and an IPN will be sent with `event_type` equal to: `FIAT_WITHDRAWAL.PENDING`. # Crypto as a Service Webhooks Source: https://docs.ripio.com/crypto-as-a-service/webhooks/crypto-as-a-service-webhooks Documentation of Crypto as a Service Webhooks The B2B API offers a webhook mechanism to notify about events that occur asynchronously. For Crypto as a Service (CaaS), webhooks are used to inform about various platform events including user management actions (banning/unbanning), transaction events (deposits, withdrawals, trades), and automated operations (AutoSwap completions). ### Ban User Webhook When a user is banned from the platform, a notification will be sent with the following structure: ```json theme={null} { "payload": { "reason": "Violated terms & conditions", "is_banned": true, "updated_at": "2025-09-17T11:00:22.433079Z", "status": "BANNED", "end_user_id": "test_coelsa_130625_1" }, "message_id": "0f1c9a3e-5b7d-4c21-9f0a-6d2e8b41c7a5", "event_type": "USER.BANNED", "issue_datetime": "2025-09-24T13:41:20.609755Z" } ``` | Key | Type | Description | | - | - | - | | `payload` | *object* | Contains the webhook payload data. | | `reason` | *string* | The reason why the user was banned. | | `is_banned` | *boolean* | Indicates the user's banned status. Will be `true` for ban events. | | `updated_at` | *string* | ISO datetime indicating when the user status was updated. | | `end_user_id` | *string* | End user's external ref identifier. | | `status` | *string* | Current status of the user. Will be `BANNED` for ban events. | | `message_id` | *string* | Unique identifier of this message. The same message delivered again on a retry, or read from the [List IPN Messages endpoint](/crypto-as-a-service/ipn-messages/list-ipn-messages), carries this same value: use it to discard duplicates. | | `event_type` | *string* | Type of event. Will be `USER.BANNED` for ban events. | | `issue_datetime` | *string* | ISO datetime indicating when the webhook was issued. | ### Unban User Webhook When a user is unbanned from the platform, a notification will be sent with the following structure: ```json theme={null} { "payload": { "reason": "Allowed to operate", "is_banned": false, "updated_at": "2025-09-17T11:00:34.584949Z", "status": "ACTIVE", "end_user_id": "test_coelsa_130625_1" }, "message_id": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d", "event_type": "USER.ACTIVE", "issue_datetime": "2025-09-24T13:41:20.609755Z" } ``` | Key | Type | Description | | - | - | - | | `payload` | *object* | Contains the webhook payload data. | | `reason` | *string* | The reason why the user was unbanned. | | `is_banned` | *boolean* | Indicates the user's banned status. Will be `false` for unban events. | | `updated_at` | *string* | ISO datetime indicating when the user status was updated. | | `status` | *string* | Current status of the user. Will be `ACTIVE` for unban events. | | `end_user_id` | *string* | End user's external ref identifier. | | `message_id` | *string* | Unique identifier of this message. The same message delivered again on a retry, or read from the [List IPN Messages endpoint](/crypto-as-a-service/ipn-messages/list-ipn-messages), carries this same value: use it to discard duplicates. | | `event_type` | *string* | Type of event. Will be `USER.ACTIVE` for active events. | | `issue_datetime` | *string* | ISO datetime indicating when the webhook was issued. | ### Withdrawal Webhook When a withdrawal is completed, a notification will be sent with the following structure: ```json theme={null} { "payload": { "id": "1356b568-e6e6-445a-b9ec-e8dd9a2839fd", "hash": "0xa56fd57368d4776d6a3f9703729c5cb4e91a70c27212bb4bf248f89f63338959", "amount": "0.000200000000000000", "status": "COMPLETED", "txn_id": "3ee05362-0249-4bcc-a41c-e1a2a56c87fd", "network": "ETHEREUM", "op_type": "WITHDRAWAL", "created_at": "2024-01-11T19:43:11.229494Z", "create_date": "2024-01-11T19:43:11.229494Z", "end_user_id": "438ea6b0d5ca0ae7601ac7acc84d57f7a3f56fac7f66d5a59429c1c636f1af1c", "currency_code": "ETH", "confirmation_date": "2024-01-11T19:43:26.881527Z", "address_destination": "0x6f2777420B02bF41F7e2dd62112bDEE230778B3e" }, "message_id": "2c4e6a80-9b1d-4f35-a7c8-3e5d7f9b1a2c", "event_type": "WITHDRAWAL.COMPLETED", "issue_datetime": "2025-09-24T13:41:20.609755Z" } ``` | Key | Type | Description | | - | - | - | | `payload` | *object* | Contains the webhook payload data. | | `id` | *string* | Unique identifier for the withdrawal. | | `hash` | *string* | Transaction hash on the blockchain. | | `amount` | *string* | Amount withdrawn in the specified currency. | | `status` | *string* | Status of the withdrawal. Will be `COMPLETED` for completed withdrawals. | | `txn_id` | *string* | Internal transaction identifier. | | `network` | *string* | Blockchain network used for the withdrawal. | | `op_type` | *string* | Operation type. Will be `WITHDRAWAL` for withdrawal events. | | `created_at` | *string* | ISO datetime indicating when the withdrawal was created. | | `create_date` | *string* | ISO datetime indicating when the withdrawal was created. | | `end_user_id` | *string* | End user's external ref identifier. | | `currency_code` | *string* | Currency code of the withdrawn asset. | | `confirmation_date` | *string* | ISO datetime indicating when the withdrawal was confirmed. | | `address_destination` | *string* | Destination address for the withdrawal. | | `message_id` | *string* | Unique identifier of this message. The same message delivered again on a retry, or read from the [List IPN Messages endpoint](/crypto-as-a-service/ipn-messages/list-ipn-messages), carries this same value: use it to discard duplicates. | | `event_type` | *string* | Type of event. Will be `WITHDRAWAL.COMPLETED` for completed withdrawals. | | `issue_datetime` | *string* | ISO datetime indicating when the webhook was issued. | ### Deposit Webhook When a deposit is completed, a notification will be sent with the following structure: ```json theme={null} { "payload": { "id": "fa42ad82-5854-4b62-bc1f-fe27b51f856d", "hash": "0xb8762f8ed09c0d38762522a233b2168ce7e6acbadab51b80fabf19ea32e030d9", "index": 0, "amount": "0.000200000000000000", "reason": "", "status": "COMPLETED", "txn_id": "ea297b09-08d7-4ce1-a2d4-f463fc0866f4", "network": "ETHEREUM", "op_type": "DEPOSIT", "created_at": "2024-01-12T08:51:22.126231Z", "is_blocked": false, "risk_score": null, "create_date": "2024-01-12T08:51:22.126231Z", "end_user_id": "438ea6b0d5ca0ae7601ac7acc84d57f7a3f56fac7f66d5a59429c1c636f1af1c", "currency_code": "ETH", "confirmation_date": "2024-01-12T08:54:56.740701Z", "address_destination": "0x6f2777420B02bF41F7e2dd62112bDEE230778B3e" }, "message_id": "3d5f7b91-0a2c-4e46-b8d9-4f6e8a0c2b3d", "event_type": "DEPOSIT.COMPLETED", "issue_datetime": "2025-09-24T13:41:20.609755Z" } ``` | Key | Type | Description | | - | - | - | | `payload` | *object* | Contains the webhook payload data. | | `id` | *string* | Unique identifier for the deposit. | | `hash` | *string* | Transaction hash on the blockchain. | | `index` | *number* | Transaction output index. | | `amount` | *string* | Amount deposited in the specified currency. | | `reason` | *string* | Additional reason of the blocked deposit. | | `status` | *string* | Status of the deposit. Will be `COMPLETED` for completed deposits. | | `txn_id` | *string* | Internal transaction identifier. | | `network` | *string* | Blockchain network used for the deposit. | | `op_type` | *string* | Operation type. Will be `DEPOSIT` for deposit events. | | `created_at` | *string* | ISO datetime indicating when the deposit was created. | | `is_blocked` | *boolean* | Indicates if the deposit is blocked. | | `risk_score` | *string* | Risk screening level of the deposit: `LOW`, `MEDIUM`, `HIGH` or `SEVERE`. Null when risk screening is not enabled for the account or the deposit was not screened. | | `create_date` | *string* | ISO datetime indicating when the deposit was created. | | `end_user_id` | *string* | End user's external ref identifier. | | `currency_code` | *string* | Currency code of the deposited asset. | | `confirmation_date` | *string* | ISO datetime indicating when the deposit was confirmed. | | `address_destination` | *string* | Destination address for the deposit. | | `message_id` | *string* | Unique identifier of this message. The same message delivered again on a retry, or read from the [List IPN Messages endpoint](/crypto-as-a-service/ipn-messages/list-ipn-messages), carries this same value: use it to discard duplicates. | | `event_type` | *string* | Type of event. Will be `DEPOSIT.COMPLETED` for completed deposits. | | `issue_datetime` | *string* | ISO datetime indicating when the webhook was issued. | ### Trade Webhook When a trade is completed, a notification will be sent with the following structure: ```json theme={null} { "payload": { "id": "09035e55-c8d2-4740-ab10-a32f04003fed", "rate": "102290.08391854", "status": "COMPLETED", "txn_id": "5e7e1bf3-8870-4672-8feb-10ebc8cfe21e", "op_type": "BUY", "quote_id": "0e3d93c5-f7cc-4c52-9281-3d6657e171e4", "base_asset": "LTC", "base_amount": "0.01759701", "charged_fee": "35.29409033", "create_date": "2023-11-01T15:46:28.835186Z", "end_user_id": "END_USER_5", "market_rate": "100284.39599857", "quote_asset": "ARS", "external_ref": "test", "quote_amount": "1800.00000000" }, "message_id": "4e6a8c02-1b3d-4f57-c9ea-5a7f9b1d3c4e", "event_type": "TRADE.COMPLETED", "issue_datetime": "2025-09-24T13:41:20.609755Z" } ``` | Key | Type | Description | | - | - | - | | `payload` | *object* | Contains the webhook payload data. | | `id` | *string* | Unique identifier for the trade. | | `rate` | *string* | Exchange rate used for the trade. | | `status` | *string* | Status of the trade. Will be `COMPLETED` for completed trades. | | `txn_id` | *string* | Internal transaction identifier. | | `op_type` | *string* | Operation type (e.g., `BUY`, `SELL`). | | `quote_id` | *string* | Identifier of the quote used for the trade. | | `base_asset` | *string* | Base asset currency code. | | `base_amount` | *string* | Amount of the base asset. | | `charged_fee` | *string* | Fee charged for the trade. | | `create_date` | *string* | ISO datetime indicating when the trade was created. | | `end_user_id` | *string* | End user's external ref identifier. | | `market_rate` | *string* | Market rate at the time of the trade. | | `quote_asset` | *string* | Quote asset currency code. | | `external_ref` | *string* | External reference identifier. | | `quote_amount` | *string* | Amount of the quote asset. | | `message_id` | *string* | Unique identifier of this message. The same message delivered again on a retry, or read from the [List IPN Messages endpoint](/crypto-as-a-service/ipn-messages/list-ipn-messages), carries this same value: use it to discard duplicates. | | `event_type` | *string* | Type of event. Will be `TRADE.COMPLETED` for completed trades. | | `issue_datetime` | *string* | ISO datetime indicating when the webhook was issued. | ### AutoSwap Webhook When an AutoSwap is completed, a notification will be sent with the following structure: ```json theme={null} { "payload": { "id": "819ceb14-e74a-4e08-96ff-cb8e2a3e4cac", "trade": { "id": "09035e55-c8d2-4740-ab10-a32f04003fed", "rate": "102290.08391854", "status": "COMPLETED", "txn_id": "5e7e1bf3-8870-4672-8feb-10ebc8cfe21e", "op_type": "BUY", "quote_id": "0e3d93c5-f7cc-4c52-9281-3d6657e171e4", "base_asset": "ETH", "base_amount": "0.01759701", "charged_fee": "35.29409033", "create_date": "2023-11-01T15:46:28.835186Z", "end_user_id": "END_USER_5", "market_rate": "100284.39599857", "quote_asset": "LTC", "external_ref": "test", "quote_amount": "1800.00000000" }, "deposit": { "id": "c6bf5da2-6db0-4673-9220-431f460b5c26", "hash": "0x085601b3af8ba7a7fc067dec4f5ee1dbbe516093028479a03f3c4d8d7bb25422", "index": 0, "amount": "0.000200000000000000", "reason": "", "status": "PENDING", "txn_id": "e3b7c5f0-4c81-4a9d-8c65-fb135f25cab6", "network": "ETHEREUM", "op_type": "DEPOSIT", "created_at": "2024-01-11T19:33:30.643088Z", "is_blocked": false, "risk_score": null, "create_date": "2024-01-11T19:33:30.643088Z", "end_user_id": "438ea6b0d5ca0ae7601ac7acc84d57f7a3f56fac7f66d5a59429c1c636f1af1c", "auto_swap_id": "819ceb14-e74a-4e08-96ff-cb8e2a3e4cac", "currency_code": "ETH", "confirmation_date": "2024-01-11T19:40:35.621612Z", "address_destination": "0x6f2777420B02bF41F7e2dd62112bDEE230778B3e" }, "withdrawal": { "id": "1356b568-e6e6-445a-b9ec-e8dd9a2839fd", "hash": "0xa56fd57368d4776d6a3f9703729c5cb4e91a70c27212bb4bf248f89f63338959", "amount": "0.000200000000000000", "status": "COMPLETED", "txn_id": "3ee05362-0249-4bcc-a41c-e1a2a56c87fd", "network": "ETHEREUM", "op_type": "WITHDRAWAL", "created_at": "2024-01-11T19:43:11.229494Z", "create_date": "2024-01-11T19:43:11.229494Z", "end_user_id": "438ea6b0d5ca0ae7601ac7acc84d57f7a3f56fac7f66d5a59429c1c636f1af1c", "currency_code": "LTC", "confirmation_date": "2024-01-11T19:43:26.881527Z", "address_destination": "0x6f2777420B02bF41F7e2dd62112bDEE230778B3e" }, "create_date": "2025-03-21T19:23:19.099723Z", "update_date": "2025-03-21T19:23:19.099760Z", "auto_swap_id": "819ceb14-e74a-4e08-96ff-cb8e2a3e4cac", "error_reason": null }, "message_id": "5f7b9d13-2c4e-4a68-d0fb-6b8a0c2e4d5f", "event_type": "AUTOSWAP.UPDATE", "issue_datetime": "2025-09-24T13:41:20.609755Z" } ``` | Key | Type | Description | | - | - | - | | `payload` | *object* | Contains the webhook payload data. | | `id` | *string* | Unique identifier for the AutoSwap. | | `trade` | *object* | Trade object containing all trade-related information. | | `deposit` | *object* | Deposit object containing all deposit-related information. | | `withdrawal` | *object* | Withdrawal object containing all withdrawal-related information. | | `create_date` | *string* | ISO datetime indicating when the AutoSwap was created. | | `update_date` | *string* | ISO datetime indicating when the AutoSwap was last updated. | | `auto_swap_id` | *string* | Unique identifier for the AutoSwap operation. | | `error_reason` | *string* | Error reason if the AutoSwap failed (can be null). | | `message_id` | *string* | Unique identifier of this message. The same message delivered again on a retry, or read from the [List IPN Messages endpoint](/crypto-as-a-service/ipn-messages/list-ipn-messages), carries this same value: use it to discard duplicates. | | `event_type` | *string* | Type of event. Will be `AUTOSWAP.UPDATE` for completed AutoSwaps. | | `issue_datetime` | *string* | ISO datetime indicating when the webhook was issued. | The nested `trade`, `deposit`, and `withdrawal` objects contain the same fields as their respective individual webhook payloads described above. # Webhooks Introduction Source: https://docs.ripio.com/crypto-as-a-service/webhooks/introduction Understanding and using webhooks with the Ripio Crypto-as-a-Service API. ## Webhook Signature Validation To ensure the security and integrity of webhook notifications, Ripio signs the request payload using the ECDSA (Elliptic Curve Digital Signature Algorithm) with the P-256 curve. This allows you to verify that the webhook payload was sent by Ripio and has not been tampered with during transmission. ### Overview of Signature Validation Webhook requests from Ripio will include an `X-Signature-Ecdsa-Sha256` header. This header contains the ECDSA signature of the request payload. You will need to use Ripio's public key to verify this signature. ### Validating the Signature: Step-by-Step Guide 1. **Retrieve the Signature Header:** Extract the value of the `X-Signature-Ecdsa-Sha256` header from the incoming webhook request. The signature is Base64 encoded. 2. **Extract the Raw Payload:** Get the raw JSON payload from the body of the webhook request. It is crucial to use the raw, unmodified payload as it was received. 3. **Verify the Signature:** Using Ripio's public key for the P-256 curve, verify the signature against the raw payload. If the signature is valid, the webhook is authentic. ### Important Notes for Signature Validation * **Exact Payload:** Ensure that the payload used for verification is exactly as it was sent by Ripio. Any modification will cause the verification to fail. The JSON payload must be serialized into a compact string, with no whitespace after separators (e.g., `{"key":"value"}` instead of `{"key": "value"}`). * **Secure Public Key Storage:** Store Ripio's public key securely. * **Reject Invalid Requests:** Always reject requests with missing or invalid signatures. ## Webhook Events For details on specific webhook events and their payload structures, please refer to the following pages: * **[BaaS Webhooks](./baas-webhooks)** * **[Crypto-as-a-Service Webhooks](./crypto-as-a-service-webhooks)** ## Identifying a Message Every message carries a `message_id`, unique to it, alongside `event_type`, `issue_datetime` and `payload`: ```json theme={null} { "message_id": "0f1c9a3e-5b7d-4c21-9f0a-6d2e8b41c7a5", "payload": { "...": "..." }, "event_type": "WITHDRAWAL.COMPLETED", "issue_datetime": "2024-01-11T19:43:26.881527Z" } ``` This is the value to key your idempotency on. A retry re-sends the very same body, and the endpoints below return that same body unchanged, so a message you have already processed is recognizable by its `message_id` no matter how it reaches you. Note that `payload.id` is not enough on its own: a single operation is notified more than once as it advances, and each of those notifications is a different message. If your account has more than one webhook configured, each one receives its own copy of every event, and each copy is a message of its own with its own `message_id`. ## Retrieving Past Messages Every message issued for your account is kept, and can be read later through the [List IPN Messages endpoint](/crypto-as-a-service/ipn-messages/list-ipn-messages), or one at a time with [Retrieve IPN Message](/crypto-as-a-service/ipn-messages/retrieve-ipn-message). Each one comes back exactly as it was delivered. This is what to reach for when a notification was missed because your endpoint was unavailable, or when you need to reconcile what Ripio sent against what you processed. ### Recovering after an outage A message your endpoint never acknowledged with a `2xx` stays `PENDING`, whether it is still being retried or has already run out of retries. So the messages you missed are: ``` GET /api/v1/ipn/messages/?status=PENDING&ordering=id ``` Read the pages in ascending `id` order, as above: new messages keep arriving while you page through, and ascending order leaves them at the end instead of shifting what you have not read yet. Then discard by `message_id` whatever you had already processed, and handle the rest. To reconcile a window rather than a failure, filter by date instead — `date_gte` and `date_lte` — and compare against what you have on your side. ## Configuring Your Webhook Endpoint To receive webhook notifications, you need to configure a publicly accessible HTTPS URL endpoint in your Ripio partner settings. Ripio will send POST requests to this URL with a JSON payload. **Key considerations for your endpoint:** * **HTTPS:** Your endpoint URL must use HTTPS. * **Respond Quickly:** Your endpoint should acknowledge receipt of the webhook by returning a `2xx` HTTP status code (e.g., `200 OK` or `202 Accepted`) as quickly as possible, ideally within 10 seconds as per Ripio's guidelines. * **Asynchronous Processing:** For any time-consuming processing of the webhook data, perform it asynchronously (e.g., using a message queue) to ensure your endpoint responds promptly. * **Idempotency:** Design your webhook handler to be idempotent. This means that processing the same event multiple times should not result in duplicated actions or inconsistent state, as network issues or retries might cause a webhook to be delivered more than once. For specific instructions on configuring your webhook URL within your Ripio account, please reach out to the Ripio technical team. ## Basic Endpoint Implementation Examples Here are basic examples of how you might set up an endpoint to receive webhooks and validate ECDSA signatures. ### Python (Flask Example) ```python theme={null} from flask import Flask, request, abort import json import base64 from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.asymmetric import ec from cryptography.exceptions import InvalidSignature app = Flask(__name__) # Replace with your actual public key provided by Ripio RIPIO_PUBLIC_KEY = """-----BEGIN PUBLIC KEY----- MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE... -----END PUBLIC KEY-----""" def verify_signature(payload_bytes: bytes, signature_header: str, public_key_pem: str) -> bool: """Verifies the ECDSA signature of the raw payload.""" try: public_key = ec.from_pem_public_key(public_key_pem.encode("utf-8")) signature = base64.b64decode(signature_header) public_key.verify(signature, payload_bytes, ec.ECDSA(hashes.SHA256())) return True except (InvalidSignature, TypeError, ValueError): return False @app.route('/ripio-webhook-handler', methods=['POST']) def ripio_webhook_handler(): received_signature = request.headers.get('X-Signature-Ecdsa-Sha256') # Get raw body bytes for signature verification raw_payload_bytes = request.get_data() if not received_signature: print("Error: Missing X-Signature-Ecdsa-Sha256 header") abort(400, 'Missing signature header') if not raw_payload_bytes: print("Error: Missing payload") abort(400, 'Missing payload') if not verify_signature(raw_payload_bytes, received_signature, RIPIO_PUBLIC_KEY): print(f"Error: Invalid signature. Received: {received_signature}") abort(403, 'Invalid signature') # If signature is valid, parse the JSON payload for processing try: payload_json = json.loads(raw_payload_bytes.decode('utf-8')) except json.JSONDecodeError: print("Error: Could not decode JSON payload") abort(400, 'Invalid JSON payload') print("Webhook received and signature validated!") print("Event Type:", payload_json.get("eventType")) print("Payload:", payload_json) # TODO: Add your asynchronous business logic here # (e.g., add to a queue for processing) return "Webhook processed successfully", 200 if __name__ == '__main__': # For development only. Use a proper WSGI server in production. app.run(port=5000, debug=True) ``` ### Node.js (Express Example) ```javascript theme={null} const express = require('express'); const crypto = require('crypto'); const bodyParser = require('body-parser'); const app = express(); const port = 3000; // Replace with your actual public key provided by Ripio const RIPIO_PUBLIC_KEY = `-----BEGIN PUBLIC KEY----- MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE... -----END PUBLIC KEY-----`; // Middleware to get raw body for signature verification app.use(bodyParser.json({ verify: (req, res, buf) => { if (buf && buf.length) { req.rawBody = buf; // Keep it as a buffer } } })); app.post('/ripio-webhook-handler', (req, res) => { // Headers are lowercased by some servers/frameworks const receivedSignature = req.headers['x-signature-ecdsa-sha256']; if (!receivedSignature) { console.error('Error: Missing X-Signature-Ecdsa-Sha256 header'); return res.status(400).send('Missing signature header'); } if (!req.rawBody) { console.error('Error: Missing raw body payload'); return res.status(400).send('Missing payload'); } try { const verify = crypto.createVerify('sha256'); verify.update(req.rawBody); const signatureBuffer = Buffer.from(receivedSignature, 'base64'); const isSignatureValid = verify.verify(RIPIO_PUBLIC_KEY, signatureBuffer); if (!isSignatureValid) { console.error(`Error: Invalid signature.`); return res.status(403).send('Invalid signature'); } // Signature is valid, req.body contains the parsed JSON payload const payload = req.body; console.log('Webhook received and signature validated!'); console.log('Event Type:', payload.eventType); console.log('Payload:', payload); // TODO: Add your asynchronous business logic here // (e.g., add to a message queue for processing) res.status(200).send('Webhook processed successfully'); } catch (error) { console.error('An error occurred during signature verification:', error); res.status(500).send('Internal server error'); } }); app.listen(port, () => { console.log(`Webhook handler listening at http://localhost:${port}/ripio-webhook-handler`); }); ``` # Theming Source: https://docs.ripio.com/crypto-as-a-service/widget/configuration/theming Match the widget to your brand: the tokens Ripio can set on your account, the values they accept, and the fonts the widget ships. The widget's look is driven by CSS custom properties (tokens). Your brand theme is a set of values for those tokens, stored on your account. Ripio loads it for you, and the widget applies it at runtime, with no rebuild and no per-partner deploy. ### How it works Send your Ripio contact the values you want to change. Anything you leave out keeps its default. A minimal theme is small: ```json theme={null} { "tokens": { "--ripio-color-primary": "#EC0000", "--ripio-btn-border-radius": "24px", "--ripio-font-family": "\"Ubuntu\"" } } ``` The widget receives the theme when it starts. It writes the tokens as custom properties on `:root`, and they inherit into its Shadow DOM. Theming is an account-level setting. There's no query parameter or runtime API for it. You can also override tokens yourself at the document level; see [Overriding tokens from your page](#overriding-tokens-from-your-page). ### Supported tokens These are the tokens your account theme can set. Any other name is rejected when the theme is loaded, so a theme built on these keeps working as the widget's internals change. **Brand** | Token | Default | Controls | | - | - | - | | `--ripio-color-primary` | `#7839ee` | Brand color: primary buttons, links, focus, selection, charts | | `--ripio-bg-brand-solid-hover` | primary, 15 % darker | Primary button hover | | `--ripio-btn-text-color-primary` | `--ripio-color-base` | Text on primary buttons | | `--ripio-color-primary-light` | primary at 7 % over the base color | Tinted brand backgrounds: tertiary buttons, selected options | Setting `--ripio-color-primary` alone is enough for a coherent theme: the hover and the tinted backgrounds are derived from it. Set them explicitly only when your brand guide defines them. **Surfaces and text** | Token | Default | Controls | | - | - | - | | `--ripio-color-base` | `#ffffff` | Base surface color | | `--ripio-bg-secondary` | gray 50 | Secondary surfaces: action tiles, segmented controls | | `--ripio-border-primary` | gray 200 | Borders of cards, inputs and the widget's frame | | `--ripio-text-primary` | `--ripio-color-text` | Main text | | `--ripio-text-secondary` | gray 700 | Secondary text and inactive tabs | | `--ripio-color-text` | gray 900 | Default text color | | `--ripio-widget-bg-color` | `--ripio-color-base` | Widget container background | | `--ripio-widget-border-color` | `--ripio-border-primary` | Widget container border | **Status and neutrals** | Token | Default | Controls | | - | - | - | | `--ripio-color-success` | success 600 | Success states | | `--ripio-color-warning` | warning 600 | Warning states | | `--ripio-color-error` | error 600 | Error states | | `--ripio-color-info` | gray 200 | Neutral informational states | | `--ripio-color-disabled` | gray 100 | Disabled backgrounds | | `--ripio-color-neutral`, `--ripio-color-neutral-10/20/50/80/100/120` | gray scale | Neutral ramp used across surfaces and secondary text | **Shapes and spacing** | Token | Default | Controls | | - | - | - | | `--ripio-btn-border-radius` | fully rounded | Buttons | | `--ripio-card-border-radius-base` | `12px` | Cards and lists (your assets, activity, market) | | `--ripio-input-border-radius` | `8px` | Inputs and menu options | | `--ripio-chip-border-radius` | fully rounded | Chips and badges | | `--ripio-widget-border-radius` | `16px` | Widget container corners | | `--ripio-widget-padding` | `24px` | Widget container padding | | `--ripio-widget-gap` | `16px` | Spacing between the widget's sections | **Typography** | Token | Default | Controls | | - | - | - | | `--ripio-font-family` | `"Lato"` | Typeface for the whole widget | ### Accepted values Each token accepts one kind of value. Anything else is rejected, including CSS functions, `url()`, `@import`, comments and anything that isn't a plain value. | Kind | Accepted | Examples | | - | - | - | | Color | `#RGB`, `#RRGGBB`, `#RRGGBBAA`, `rgb(r, g, b)`, `rgba(r, g, b, a)` | `#EC0000`, `rgba(0, 0, 0, 0.5)` | | Length (radius, padding, gap) | `0`, or a non-negative number in `px`, `rem` or `%` | `8px`, `999px`, `1.5rem` | | Font | One of the [supported fonts](#fonts), as a quoted CSS string | `"Nunito Sans"` | ### Fonts The widget ships and serves its own fonts, so you don't load anything and your page's CSP doesn't need a third-party font host. `--ripio-font-family` accepts one of: | Font | Weights | | - | - | | `"Lato"` (default) | 400, 700 | | `"Libre Franklin"` | 400, 700 | | `"Ubuntu"` | 400, 700 | | `"Nunito Sans"` | 400, 700 | | `"Source Sans 3"` | 400, 700 | The widget only downloads the family it's using. If your brand needs a typeface that isn't on the list, talk to your Ripio contact: fonts are added to the widget, never loaded from your side. ### Overriding tokens from your page You can also set tokens yourself with CSS custom properties on your page's `:root`. The order of precedence is: 1. The widget's defaults. 2. Your page's `:root`, which overrides the defaults. 3. Your account theme, which overrides both. ```css theme={null} :root { --ripio-color-primary: #034cf5; --ripio-widget-border-radius: 16px; } ``` From your page you can reach the design system's other tokens as well: per-component tokens such as `--ripio-btn-*`, `--ripio-input-*`, `--ripio-card-*`, `--ripio-chip-*`, `--ripio-toast-*` and `--ripio-overlay-*`, and the radius, shadow and type scales. They belong to the design system and may be renamed as it evolves, so prefer the [supported tokens](#supported-tokens) where they're enough. ### What not to override The design-system primitives (`--violet-*`, `--gray-*`, `--error-*`, `--warning-*`, `--success-*`) and the transaction status-chip tokens `--st-*` are internal. They're the raw palette the semantic tokens are built from, they aren't part of the contract, and they can change without notice. Override the semantic layer instead. Theming only repaints the widget. It can't reposition, resize or hide anything inside it. The widget's own classes, such as `.btn-primary`, live inside its Shadow DOM and your page's selectors can't reach them: use the tokens. ### Before and after The widget with Ripio's default theme The same widget with a partner's brand theme applied # Buy and sell Source: https://docs.ripio.com/crypto-as-a-service/widget/flows/buy-and-sell The quote-then-confirm flow a user goes through to buy or sell crypto. Buying and selling share the same flow: pick a side and an amount, get a quote with a rate guaranteed for a short window, then confirm. The user toggles between **Buy** and **Sell**, picks an asset, and enters an amount — either typed or from a preset chip. A reference rate is shown, but it isn't guaranteed until the next step. Step 1: choosing an asset and an amount The widget requests a quote and shows the guaranteed rate, the fee, and the amount the user will receive, together with a countdown. While it's running, the rate holds. Step 2: reviewing the quote before confirming If the countdown reaches zero before the user confirms, the widget automatically requests a new quote and shows a banner explaining the rate refreshed. Confirming shows a processing state, then either success — with a receipt the user can view — or an error with the option to retry. ### Buy needs your approval Before a **buy** executes, the widget asks your backend to approve it — the operation only goes through once you respond with an approval signed with your Ed25519 key. See [Webhooks](/crypto-as-a-service/widget/webhooks#transaction_approval_request) for the request your endpoint receives and how to respond to it. A **sell** doesn't need approval: Ripio holds the custody of the crypto being sold, so there's nothing on your side to authorize before it executes. You still receive the result once it's done. ### After the operation Either way, your backend receives a `transaction_result` webhook once the operation resolves — successfully or not — so you can update your own records of the user's balance. See [Webhooks](/crypto-as-a-service/widget/webhooks#transaction_result). The operation also appears in the user's [Activity](/crypto-as-a-service/widget/flows/home#activity), where they can open it to see its detail and download a receipt. # Home Source: https://docs.ripio.com/crypto-as-a-service/widget/flows/home The widget's landing screen: balance, holdings, activity and the market. Home is the first screen a user sees after the widget finishes loading. It's organized into four sections. The widget's Home screen ### Balance and actions The total balance valued in the account's fiat currency, its change, and the action buttons — Buy, Sell, Convert, Deposit, Send — filtered down to the operations the session has enabled (see [Overview](/crypto-as-a-service/widget/overview#what-depends-on-your-configuration)). The balance's daily change reflects the change in **price** of the user's holdings, not a change in the size of their balance. ### Your assets Each holding the user has, with its 24-hour change and, where the token is enrolled in a yield program, its APY. A zero balance isn't shown — this is a list of what the user owns, not a catalog of every asset available. ### Activity The user's recent operations, each with its status and a link into its detail. See [Buy and sell](/crypto-as-a-service/widget/flows/buy-and-sell) for how an operation gets there. ### Market Prices and short-term trends for the tokens available on the account, independent of what the user holds. A `null` value in a change percentage or price means that figure genuinely isn't available right now — not zero. The widget hides the corresponding chip or shows an empty state rather than a misleading `0%`. # Authentication Source: https://docs.ripio.com/crypto-as-a-service/widget/get-started/authentication How to generate a one-time session code for the crypto widget. ### Before you begin * **Credentials.** Contact Ripio to request your partner registration and obtain the `client_id` and `client_secret` that uniquely identify you as a Ripio customer. It is your responsibility to secure them. * **A signing key for webhooks.** Issued alongside your credentials — see [Webhooks](/crypto-as-a-service/widget/webhooks). This is the only widget endpoint you call directly, and it must be called from your **server**, never from the browser: `client_secret` should never reach the end user's device. Every other request — quotes, execution, portfolio, activity — is made by the widget itself on the user's behalf, so there is no other widget API for you to integrate against. ### How a session starts You never handle the user's session token. Your server asks Ripio for a **session code**, you hand that code to the widget, and the widget exchanges it for the session on its own as soon as it loads. 1. Your server calls `POST /auth` and receives a `session_code`. 2. You pass the code to the widget — as the `code` property when [embedding it](/crypto-as-a-service/widget/get-started/embedding), or in the URL fragment for a [WebView](/crypto-as-a-service/widget/get-started/webview). 3. The widget redeems the code, removes it from the URL and the page, and starts the user's session. A session code works **once** and expires after **2 minutes**. That is what makes it safe to pass through a URL or a page attribute: by the time it could show up in a log, a browser history entry or a support screenshot, the widget has already used it. ### Generate a session code Endpoint: `https://b2b-crypto-widget-api.sandbox.ripio.com/api/v1/auth` Method: `POST` · Content-Type: `application/json` | Field | Type | Description | | - | - | - | | `client_id` | *string* | Identifies your account. | | `client_secret` | *string* | Your account's secret. Never send this from the browser. | | `external_ref` | *string* | A unique, unrepeatable identifier for the user in your own system. Used to correlate webhooks and to map the widget session back to your user. | | `requested_operations` | *array of string* (optional) | Narrow this session to a subset of `deposit`, `withdrawal`, `swap`, `yields`, `loan`, `buy`, `sell`. Omit it to grant everything your account has enabled. | Request: ```bash theme={null} curl \ --location --request POST 'https://b2b-crypto-widget-api.sandbox.ripio.com/api/v1/auth' \ --header 'Content-Type: application/json' \ --data '{ "client_id": "", "client_secret": "", "external_ref": "" }' ``` Response: ```json theme={null} { "session_code": "k7Qe2Vn9Hc4yR1bT0sWmZpL8xA3dFgJ6uEoNiKqYtUw", "expires_in": 120 } ``` | Field | Description | | - | - | | `session_code` | Pass it to the widget to start the user's session. See [Embedding the widget](/crypto-as-a-service/widget/get-started/embedding). | | `expires_in` | Seconds the code stays redeemable. | **Request the code right before you open the widget** — not when the user logs into your app, and never cached for later. It expires in 2 minutes and works only once: a code that was already used, or that you reuse for a second widget, starts no session. Mint a new one for every time the widget is opened. Once redeemed, the session lasts **one hour** from the moment you called `POST /auth`. When it ends, the widget tells you so you can hand it a fresh code without reloading your page — see [Session lifecycle](/crypto-as-a-service/widget/get-started/session-lifecycle). ### Narrowing a session ```json theme={null} { "client_id": "", "client_secret": "", "external_ref": "", "requested_operations": ["buy", "sell"] } ``` This session can only buy and sell, regardless of what else your account has enabled — useful when you want a specific entry point of your app to expose a narrower widget than your account is capable of. ### Errors | Cause | What happens | | - | - | | Invalid `client_id` or `client_secret` | `401`. The same generic error either way — Ripio doesn't distinguish "unknown client" from "wrong secret". | | Too many requests for your `client_id` | `429`. The endpoint is rate-limited per account regardless of the caller's IP. | See [Troubleshooting](/crypto-as-a-service/widget/troubleshooting) for the full error contract used across the widget's endpoints. # Embedding the widget Source: https://docs.ripio.com/crypto-as-a-service/widget/get-started/embedding The script tag and the web component's attributes. The widget is a native [web component](https://developer.mozilla.org/en-US/docs/Web/Web_Components). To use it, import the script, mount the element in your page, and hand it the [session code](/crypto-as-a-service/widget/get-started/authentication) your server just requested: ```html theme={null} ``` `` is the **only** public element. It works in any framework that supports native web components — React, Next.js, Vue, Angular — or standalone HTML. ### Versions and integrity There's no fixed `bundle.js`. Each release publishes a versioned file, `bundle.v..js`, and its [Subresource Integrity](https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity) hash. Your Ripio contact sends you both with each release. Pin them in your snippet and update them when you adopt a new release: * With `integrity`, the browser refuses to run the file if a single byte differs from the release you pinned, so a tampered CDN can't run code inside your page. * `crossorigin="anonymous"` is required alongside `integrity`. * The script tag is the only file you reference. The design tokens and translations ship inside the bundle. The widget loads its [fonts](/crypto-as-a-service/widget/configuration/theming#fonts) by itself, from the same host. ### Content Security Policy When the widget is embedded in your page, your page's CSP applies to it. It needs to allow: | Directive | Value | Why | | - | - | - | | `script-src` | The widget's host | The bundle | | `connect-src` | The widget API's host | The widget's only backend | | `font-src` | The widget's host | The fonts the widget serves | | `style-src` | `'unsafe-inline'` | The widget injects its design tokens and your account theme as `