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

# Start Merchant Onboarding

> Create (or resume) a merchant's Stripe Connect account and return an embedded onboarding session

Ensures the organization has a Stripe **connected account** — creating one on the platform if it doesn't exist yet — and returns everything you need to run Stripe's embedded onboarding: a Connect **Account Session `client_secret`** and the platform **`publishable_key`**. The merchant submits their own banking and identity details directly to Stripe. The platform never receives or stores merchant API keys.

The call is safe to repeat: account creation is idempotent per organization, and each call mints a fresh, short-lived session secret. Use it both to start onboarding and to resume it (e.g. the merchant closed the tab before finishing).

<Warning>
  **Onboarding requires a browser front-end** — unlike the other Payments endpoints, you can't complete it from a backend alone. Stripe collects the merchant's identity, tax, and bank details inside an iframe rendered by Stripe's embedded [Connect components](https://docs.stripe.com/connect/get-started-connect-embedded-components). Your server calls this endpoint to mint the session; your front-end renders the component with the returned values. The full flow is shown below.
</Warning>

<Note>
  This endpoint prepares onboarding; it does **not** by itself make a merchant charge-ready. Poll [`GET /v1/organizations/{organization_id}/payments/status`](/api-reference/payments/status) after the merchant completes Stripe's flow to confirm `can_accept_payments` is `true`.
</Note>

## What you need

| Piece                                             | Where it comes from                                                                   |
| ------------------------------------------------- | ------------------------------------------------------------------------------------- |
| `client_secret`                                   | This endpoint (short-lived, mint one per attempt)                                     |
| `publishable_key`                                 | This endpoint — Telzino's platform key (`pk_…`). Public by design; safe in browser JS |
| `@stripe/connect-js` + `@stripe/react-connect-js` | Stripe's npm packages, installed in your front-end                                    |

You do **not** need a Stripe account or any Stripe key of your own — the connected account lives on Telzino's platform.

## Path Parameters

<ParamField path="organization_id" type="string" required>
  UUID of the organization (merchant) to onboard. Must belong to your account.

  **Example:** `123e4567-e89b-12d3-a456-426614174000`
</ParamField>

<RequestExample>
  ```bash cURL theme={null}
  curl -X POST \
    "https://api.telzino.com/v1/organizations/123e4567-e89b-12d3-a456-426614174000/payments/onboarding" \
    -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
  ```

  ```javascript Server (mint the session) theme={null}
  // Keep your Telzino access token server-side. Expose a tiny endpoint your
  // front-end can call to fetch a fresh session for a given merchant org.
  app.post('/onboarding/:orgId', async (req, res) => {
    const r = await fetch(
      `https://api.telzino.com/v1/organizations/${req.params.orgId}/payments/onboarding`,
      { method: 'POST', headers: { Authorization: `Bearer ${process.env.TELZINO_TOKEN}` } }
    );
    const { client_secret, publishable_key } = await r.json();
    res.json({ client_secret, publishable_key });
  });
  ```

  ```jsx Front-end (render Stripe's embedded onboarding) theme={null}
  import { useState } from 'react';
  import { loadConnectAndInitialize } from '@stripe/connect-js';
  import { ConnectComponentsProvider, ConnectAccountOnboarding } from '@stripe/react-connect-js';

  export function Onboarding({ orgId }) {
    const [instance, setInstance] = useState(null);

    const begin = async () => {
      // Fetch publishable_key + a fresh client_secret from YOUR server (above).
      const first = await fetch(`/onboarding/${orgId}`, { method: 'POST' }).then((r) => r.json());
      const conn = loadConnectAndInitialize({
        publishableKey: first.publishable_key,
        // Stripe calls this to (re)fetch a session secret on demand.
        fetchClientSecret: async () =>
          (await fetch(`/onboarding/${orgId}`, { method: 'POST' }).then((r) => r.json())).client_secret,
      });
      setInstance(conn);
    };

    if (!instance) return <button onClick={begin}>Set up payments</button>;

    return (
      <ConnectComponentsProvider connectInstance={instance}>
        <ConnectAccountOnboarding onExit={() => {/* re-check status via /payments/status */}} />
      </ConnectComponentsProvider>
    );
  }
  ```

  ```python Server (mint the session) theme={null}
  import os, requests

  def create_onboarding_session(org_id: str) -> dict:
      r = requests.post(
          f'https://api.telzino.com/v1/organizations/{org_id}/payments/onboarding',
          headers={'Authorization': f"Bearer {os.environ['TELZINO_TOKEN']}"},
      )
      data = r.json()
      # Return both to your front-end, which renders Stripe's embedded component.
      return {'client_secret': data['client_secret'], 'publishable_key': data['publishable_key']}
  ```
</RequestExample>

<ResponseExample>
  ```json 200 theme={null}
  {
    "organization_id": "123e4567-e89b-12d3-a456-426614174000",
    "client_secret": "accs_secret_1a2b3c...",
    "publishable_key": "pk_live_51Ab..."
  }
  ```

  ```json 400 theme={null}
  {
    "error": "Invalid organization_id"
  }
  ```

  ```json 401 theme={null}
  {
    "error": "unauthorized",
    "error_description": "User payload not found"
  }
  ```

  ```json 403 No access theme={null}
  {
    "error": "Forbidden",
    "error_description": "You do not have access to this organization"
  }
  ```

  ```json 403 Feature locked theme={null}
  {
    "error": "forbidden",
    "error_description": "Card payments are not available for this organization"
  }
  ```

  ```json 404 theme={null}
  {
    "error": "Organization not found"
  }
  ```
</ResponseExample>

## Response Fields

| Field             | Type           | Description                                                                                                                                            |
| ----------------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `organization_id` | string         | UUID of the merchant the session is for                                                                                                                |
| `client_secret`   | string         | Short-lived Stripe Account Session secret for the embedded onboarding component                                                                        |
| `publishable_key` | string \| null | Telzino's platform Stripe publishable key (`pk_…`) for `loadConnectAndInitialize`. Public by design; `null` only if the platform hasn't configured one |

<Warning>
  The `client_secret` is short-lived and single-purpose. Fetch it server-side per onboarding attempt and hand it straight to the Stripe component — don't cache or log it. The `publishable_key`, by contrast, is **not** secret — it is meant to be embedded in browser JavaScript, so caching it client-side is fine.
</Warning>

## Feature Administration gating

When [Feature Administration](/admin-guide/dashboard/feature-administration) is enabled and **Card Payments (Stripe)** is locked for the organization — directly or inherited from its reseller or partner — this endpoint returns `403 forbidden` and no onboarding session is created.

Only the entry point is gated. A merchant that already completed onboarding keeps its connected account and continues to settle, [payment status](/api-reference/payments/status) stays readable, and in-flight charges always complete.
