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

# Backend integration

> What your backend does for the Mobile SDK: issue widget sessions, receive the terms acceptance webhook and register the user with the Stack API.

Your backend is the bridge between the widget and the [Kravata Stack API](/stack/overview). It does three things:

1. Gives your app a widget session when the user opens the widget.
2. Receives the `user.accepted_terms` webhook.
3. Registers the user with their KYC data.

Steps 2 and 3 call the Stack API with an access token. Get it with your `apiKey` and `secretKey` as described in [Authentication](/stack/authentication).

## 1. Issue a widget session

Expose an endpoint in your backend that your app calls right before opening the widget. It returns the `sharedKey` and `clientId` from your environment variables and a new `nonce`.

```js Node.js (Express) theme={"dark"}
import { randomUUID } from "node:crypto";

app.post("/kravata/widget-session", requireLoggedInUser, (req, res) => {
  res.json({
    sharedKey: process.env.KRAVATA_SHARED_KEY,
    clientId: process.env.KRAVATA_CLIENT_ID,
    nonce: randomUUID(), // new value for every session
  });
});
```

```python Python (FastAPI) theme={"dark"}
import os, uuid

@app.post("/kravata/widget-session")
def widget_session(user=Depends(require_logged_in_user)):
    return {
        "sharedKey": os.environ["KRAVATA_SHARED_KEY"],
        "clientId": os.environ["KRAVATA_CLIENT_ID"],
        "nonce": str(uuid.uuid4()),  # new value for every session
    }
```

<Warning>
  The `nonce` protects against replay attacks. Generate it on your server for every widget session and never reuse it. Protect this endpoint with your own user authentication so only logged-in users can open the widget.
</Warning>

## 2. Receive the terms acceptance webhook

When the user accepts the Terms and Conditions inside the widget, Kravata calls your webhook with the `user.accepted_terms` event. Use it as the signal to register the user (step 3).

<Note>
  In production, Kravata registers this webhook for you during onboarding. In test you can register it yourself with [Register Webhook Endpoint](/stack/api-reference/webhooks/register-webhook-endpoint). You only need to do it once per environment.
</Note>

```bash theme={"dark"}
curl -X POST https://test-api-kore.kravata.co/api/v1/integration-endpoints \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Terms accepted",
    "direction": "OUTBOUND",
    "url": "https://your-server.example.com/webhooks/kravata/terms",
    "httpMethod": "POST",
    "authType": "NONE",
    "triggerEvent": "user.accepted_terms",
    "timeoutMs": 30000,
    "requestHeaders": { "x-api-key": "YOUR_WEBHOOK_SECRET" },
    "payloadMapping": {
      "user_id": { "from": "metadata", "where": { "name": "EXTERNAL_USER_ID" }, "pick": "value" }
    },
    "retryConfig": { "max_attempts": 3, "initial_delay_ms": 1000, "multiplier": 4 }
  }'
```

| Field | Description |
| - | - |
| `requestHeaders` | Headers Kravata sends on every call to your webhook. Add a secret, such as `x-api-key`, so your server can check that the call comes from Kravata. |
| `payloadMapping` | Shapes the body your server receives. Each key is a field of the outgoing body. The example takes the `EXTERNAL_USER_ID` item from `metadata` and sends its `value` as `user_id`. |
| `retryConfig` | Retries when your server doesn't answer `2xx`. See [Delivery](/stack/webhooks#delivery). |

### Payload

<Tabs>
  <Tab title="With payloadMapping (recommended)">
    Your server receives only the fields you mapped. With the example above, `user_id` is the ID you passed to the SDK as `userId`:

    ```json theme={"dark"}
    { "user_id": "937ab7df-dd98-4a8c-8013-19fdc3672db1" }
    ```
  </Tab>

  <Tab title="Without payloadMapping">
    Your server receives the full event:

    ```json theme={"dark"}
    {
      "userId": "b53b0138-77c4-4026-846d-684aa937744d",
      "status": "PENDING",
      "registerDate": "2026-05-04",
      "metadata": [
        { "name": "EXTERNAL_USER_ID", "value": "937ab7df-dd98-4a8c-8013-19fdc3672db1" },
        { "name": "TERMS VERSION", "value": "1.0.0" },
        { "name": "TERMS DATE", "value": "2026-05-04T05:35:55.750033+00:00" }
      ],
      "ipAddress": "203.0.113.10",
      "version": "1.0.0",
      "acceptedAt": "2026-05-04T05:35:55.750033+00:00"
    }
    ```

    <Warning>
      The root `userId` is Kravata's internal ID, not yours. Your ID is the `value` of the `EXTERNAL_USER_ID` item in `metadata`.
    </Warning>
  </Tab>
</Tabs>

### Handle the event

```js Node.js (Express) theme={"dark"}
app.post("/webhooks/kravata/terms", async (req, res) => {
  if (req.headers["x-api-key"] !== process.env.KRAVATA_WEBHOOK_SECRET) {
    return res.status(401).end();
  }
  const { user_id } = req.body; // your own user ID
  if (!user_id) return res.status(400).end();

  res.status(200).json({ received: true }); // answer fast
  await queue.add("kravata-register-user", { userId: user_id }); // step 3, async
});
```

Answer `2xx` quickly and do the registration asynchronously. Deliveries can be retried, so make the handler idempotent. See [Best practices](/stack/webhooks#best-practices).

## 3. Register the user

Call [User Registration](/stack/api-reference/users/user-registration) with the user's KYC data, the `x-integration-register` header, and **the same ID you passed to the SDK** as `external_user_id`.

```bash theme={"dark"}
curl -X POST https://test-api-kore.kravata.co/api/v1/users/register \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "x-integration-register: YOUR_INTEGRATION_ID" \
  -H "Content-Type: application/json" \
  -d '{
    "external_user_id": "937ab7df-dd98-4a8c-8013-19fdc3672db1",
    "first_name": "Juan",
    "first_surname": "Pérez",
    "birth_date": "1990-03-15",
    "cell_phone_number": "3001234567",
    "email": "juan.perez@example.com",
    "document_type": "CC",
    "document_number": "1234567890",
    "document_issue_date": "2008-06-01",
    "document_issuance_country": "Colombia"
  }'
```

<Info>
  The body field that carries your user ID depends on the field mapping of your integration: `external_user_id` by default. If Kravata configured your integration with another name, such as `user_id`, send it under that name. Either way, Kravata stores it as `EXTERNAL_USER_ID`.
</Info>

The `user_id` in the response is Kravata's internal ID for the user, not yours. Store it next to your own ID: the Stack API uses it in every user endpoint (accounts, wallets, operations).

See [Users](/stack/users) for the full field list, statuses and the KYC review.

## 4. Follow the user's status

After registration the user is reviewed automatically. Subscribe to `user.updated` to know the result:

| Status | Meaning | What to do |
| - | - | - |
| `PENDING` | The user accepted the terms; the registration is not processed yet. | Register the user (step 3). |
| `APPROVED` | KYC approved. The user can operate. | Nothing. The user can use the widget normally. |
| `REJECTED` | KYC rejected. | Tell the user and contact Kravata for the reason. |

The complete list of statuses is in [Users](/stack/users#statuses).


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