> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ripio.com/llms.txt
> Use this file to discover all available pages before exploring further.

# How the integration works

> Who calls whom in a widget integration: your backend, your app, the widget and Ripio's widget API, from opening a session to the webhooks of a buy.

A widget integration has four participants. Two are yours and two are Ripio's:

| Participant | Runs on | Role |
| - | - | - |
| **Your backend** | Your servers | Holds your credentials, requests session codes, and receives webhooks. |
| **Your app** | The user's device — a web page or a WebView | Hosts the widget and hands it a session code. |
| **The widget** | Inside your app, served by Ripio | The `<ripio-crypto-widget>` element: the screens your user operates. |
| **Widget API** | Ripio's servers | The widget's only backend. Quotes, executes and settles operations. |

### The big picture

```mermaid theme={null}
flowchart LR
  subgraph servers["Your servers"]
    BE["Your backend"]
  end
  subgraph device["The user's device"]
    direction TB
    APP["Your app<br/>web page or WebView"]
    W["Ripio widget<br/>ripio-crypto-widget"]
  end
  subgraph ripio["Ripio"]
    API["Widget API"]
  end

  BE -- "1 · POST /auth<br/>client_id + client_secret" --> API
  BE -- "2 · session_code" --> APP
  APP -- "3 · code property<br/>or URL fragment" --> W
  W -. "6 · session events" .-> APP
  W -- "4 · redeem the code, then<br/>reads, quotes, executions" --> API
  API -- "5 · webhooks<br/>signed both ways" --> BE
```

| | From → To | What travels | Credential | Details |
| - | - | - | - | - |
| 1 | Your backend → Widget API | `POST /auth`: a one-time session code for one user | `client_id` + `client_secret` | [Authentication](/crypto-as-a-service/widget/get-started/authentication) |
| 2 | Your backend → your app | The session code, over your own channel | Your own | — |
| 3 | Your app → widget | The `code` property, or `#_co=` in a WebView URL | The session code | [Embedding](/crypto-as-a-service/widget/get-started/embedding), [WebView](/crypto-as-a-service/widget/get-started/webview) |
| 4 | Widget → Widget API | Everything the user sees and does | A session token that only the widget holds | Not something you integrate |
| 5 | Widget API → your backend | `transaction_approval_request` and `transaction_result` | Ed25519 signatures, both directions | [Webhooks](/crypto-as-a-service/widget/webhooks) |
| 6 | Widget → your app | `ripio-session-expired`, `ripio-handoff` | — | [Session lifecycle](/crypto-as-a-service/widget/get-started/session-lifecycle) |

Your systems and Ripio's meet in exactly two places: **`POST /auth`**, where you call Ripio, and the **webhooks**, where Ripio calls you. Everything in between — portfolio, market, quotes, executions — goes straight from the widget to the widget API, from the user's device:

* **There's nothing to proxy.** Your app never calls the widget API, and your backend never sees the widget's requests. Your page's origin has to be registered with Ripio and its CSP has to allow the widget API — see [Infrastructure requirements](/crypto-as-a-service/widget/configuration/infrastructure).
* **You never handle the session token.** The widget gets it by redeeming the code and keeps it in memory: it never reaches your page, the URL or the browser's storage.
* **`client_secret` stays on your server.** It's only used in step 1, server to server.

### Opening the widget

Every time the widget opens, your backend requests a fresh code and the widget redeems it on its own:

```mermaid theme={null}
sequenceDiagram
  autonumber
  actor U as User
  participant APP as Your app
  participant BE as Your backend
  participant W as Widget
  participant API as Widget API

  U->>APP: Opens the crypto section
  APP->>BE: Asks for a widget session
  BE->>API: POST /auth with client_id, client_secret and external_ref
  API-->>BE: session_code, one use, expires in 2 minutes
  BE-->>APP: session_code
  APP->>W: Mounts the widget with the code
  W->>W: Reads the code and removes it from the element and the URL
  W->>API: Redeems the session code
  API-->>W: Session token, valid for 1 hour from POST /auth
  par
    W->>API: Account settings and theme
  and
    W->>API: User status
  end
  W->>W: Applies your theme
  W->>API: Portfolio, market and activity
  W-->>U: Home screen
```

Two things can stop it short of Home:

* **The code doesn't work** — it expired, was already used, or never existed. The widget shows the "session ended" screen and fires `ripio-session-expired`.
* **The user can't operate yet** — for example, their identity verification isn't complete. The widget fires `ripio-handoff` so you can take them to the right place in your app.

### Buying crypto

From the user's confirmation to your books, a buy involves your backend twice: once to approve it, if your account has transaction approval enabled, and once to learn how it ended.

```mermaid theme={null}
sequenceDiagram
  autonumber
  actor U as User
  participant W as Widget
  participant API as Widget API
  participant BE as Your backend

  U->>W: Picks Buy, an asset and an amount
  W->>API: Requests a quote
  API-->>W: Guaranteed rate, fee and expiry
  W-->>U: Quote with a countdown
  U->>W: Confirms
  W->>API: Executes the quote
  opt Transaction approval enabled on your account
    API->>BE: transaction_approval_request, signed by Ripio
    Note over API,BE: Single attempt, 5-second timeout, fail-closed
    BE-->>API: approved true + event_id, signed by you
  end
  API->>API: Executes the trade
  API-->>W: Operation completed
  W-->>U: Success screen with the operation number
  API-)BE: transaction_result COMPLETED, signed by Ripio
  BE--)API: received true + event_id, signed by you
```

How the approval step ends decides what the user sees and whether a result follows:

| Your endpoint… | The user sees | `transaction_result` |
| - | - | - |
| Approves, signed | The trade executes | `COMPLETED` — or `CANCELLED` if Ripio rejects it, e.g. for insufficient balance |
| Denies, signed | "Not approved" | None — you already know the outcome |
| Doesn't answer within 5 seconds | An error | `CANCELLED`, `transaction_approval_unavailable` |
| Answers with a missing or invalid signature | An error | `CANCELLED`, `transaction_approval_signature_invalid` |

A few things the diagram doesn't show:

* **A sell skips the approval step.** Ripio holds the custody of the crypto being sold, so there's nothing on your side to authorize. You still receive its `transaction_result`.
* **The result is asynchronous.** `transaction_result` can reach your backend before or after the widget shows the success screen — don't make one wait for the other. If you don't acknowledge it with a signed response, Ripio retries it up to 3 times, with the same `event_id`.
* **A retry can ask you again.** If confirming fails without a clear outcome — a timeout, a network error — and the user presses retry, your endpoint can receive a second `transaction_approval_request` for the same operation `id`, with a new `event_id`. Only `transaction_result` closes the operation.

See [Webhooks](/crypto-as-a-service/widget/webhooks) for the payloads, the signatures and every cancel reason.

### Renewing a session

A session lasts one hour from your `POST /auth` call and doesn't extend with activity. When it ends, the widget asks your app for a new code instead of reloading the page:

```mermaid theme={null}
sequenceDiagram
  autonumber
  participant APP as Your app
  participant BE as Your backend
  participant W as Widget
  participant API as Widget API

  Note over W,API: The hour runs out, or the widget API rejects the session
  W-->>APP: ripio-session-expired event, or a native bridge message in a WebView
  APP->>BE: Asks for a new widget session
  BE->>API: POST /auth
  API-->>BE: New session_code
  BE-->>APP: session_code
  APP->>W: renew(session_code)
  W->>API: Redeems the new code
  API-->>W: New session token
  W->>W: Starts clean on the Home screen, without reloading your page
```

The new code can belong to a different `external_ref` — the widget starts from a clean state on every renewal. See [Session lifecycle](/crypto-as-a-service/widget/get-started/session-lifecycle) for every event and the WebView bridges.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.