> For the complete documentation index, see [llms.txt](https://docs.tylt.money/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.tylt.money/tylt-embedded-wallet-service/user-management/reset-totp.md).

# Reset TOTP

Reset an end user's TOTP secret after device loss.

Resets an end user's TOTP secret after device loss. This flow does not require the current TOTP code.

The merchant backend requests an email code first. It then uses that code to rotate the TOTP secret. Never call these endpoints from a browser.

### Authentication and requirements

Both endpoints require these headers:

```http
x-tlp-apikey: <api-key>
x-tlp-signature: <hmac-signature>
```

Generate the signature using:

```
HMAC_SHA256(apiSecret, JSON.stringify(params))
```

For `GET` requests, `params` is the query object. For `POST` requests, it is the request body.

Send the query or body exactly as signed. Preserve field names, order, types, and values.

The merchant IP must be on its configured whitelist. An empty whitelist blocks every request.

The end user must meet these requirements:

* Identify them with `endUserId`, `externalUserId`, or `endUserEmail`.
* Their account must be active.

Every response uses this envelope:

```json
{
  "msg": "",
  "data": {}
}
```

`msg` is empty on success unless stated otherwise. `data` contains the response payload.

#### Common authentication errors

| HTTP status | `msg`                                                                         | Cause                                        |
| ----------- | ----------------------------------------------------------------------------- | -------------------------------------------- |
| `401`       | `API key and signature headers (x-tlp-apikey, x-tlp-signature) are required.` | A required authentication header is missing. |
| `401`       | `Invalid API key.`                                                            | The API key was not found.                   |
| `403`       | `API key or owner is inactive.`                                               | The key or merchant account is inactive.     |
| `400`       | `Invalid signature.`                                                          | The HMAC signature does not match.           |
| `403`       | `Whitelabel IP whitelist is not configured.`                                  | The merchant has no IP whitelist.            |
| `403`       | `IP not whitelisted.`                                                         | The caller IP is not allowed.                |
| `400`       | `One of endUserEmail, endUserId or externalUserId is required.`               | No end-user identifier was supplied.         |
| `404`       | `End user not found for this owner.`                                          | The user does not belong to this merchant.   |
| `403`       | `End user is suspended.`                                                      | The end-user account is suspended.           |
| `403`       | `KYC not approved.`                                                           | The end user's KYC status is not approved.   |

### Step 1: Send a reset email code

#### Endpoint

```http
GET /whitelabel/auth/sendResetTotpOTP
```

Generates a six-digit code and emails it to the end user's registered email address. The code expires after five minutes.

#### Query parameters

| Field                                            | Required     | Description                                    |
| ------------------------------------------------ | ------------ | ---------------------------------------------- |
| `endUserId`, `externalUserId`, or `endUserEmail` | One required | Identifies the end user who receives the code. |

Sign the complete query object.

#### Example request

```http
GET /whitelabel/auth/sendResetTotpOTP?externalUserId=u-xyz
x-tlp-apikey: ak_live_...
x-tlp-signature: <HMAC_SHA256(secret, JSON.stringify({ externalUserId: "u-xyz" }))>
```

#### Successful response

```http
200 OK
```

```json
{
  "msg": "",
  "data": {}
}
```

The code is one-shot. Step 2 consumes it after a successful match.

#### Rate limit and errors

You can request at most five codes per end user every 15 minutes.

| HTTP status | `msg`                                    | Cause                                                |
| ----------- | ---------------------------------------- | ---------------------------------------------------- |
| `429`       | `Rate limit exceeded for sending OTP...` | More than five requests within 15 minutes.           |
| `500`       | `Unable to send code. Contact support`   | The user was not found, or Redis or SendGrid failed. |

### Step 2: Verify the code and reset TOTP

#### Endpoint

```http
POST /whitelabel/auth/resetTotp
```

Verifies the email code and rotates the end user's TOTP secret. The previous secret becomes invalid immediately.

#### Request body

| Field                                            | Required     | Description                                                 |
| ------------------------------------------------ | ------------ | ----------------------------------------------------------- |
| `endUserId`, `externalUserId`, or `endUserEmail` | One required | Identifies the end user.                                    |
| `emailCode`                                      | Yes          | The six-digit code sent in Step 1.                          |
| `timestamp`                                      | Yes          | Unix epoch milliseconds within ±120 seconds of server time. |
| `nonce`                                          | Yes          | A unique random value used once within the allowed window.  |

Include `timestamp` and `nonce` in the signed request body. Reusing a nonce returns `409 Conflict`.

#### Example request

```http
POST /whitelabel/auth/resetTotp
x-tlp-apikey: ak_live_...
x-tlp-signature: <HMAC_SHA256(secret, JSON.stringify(body))>
Content-Type: application/json
```

```json
{
  "externalUserId": "u-xyz",
  "emailCode": "123456",
  "timestamp": 1700000000000,
  "nonce": "a1b2c3d4-e5f6"
}
```

### Code snippet

Run this example from the merchant's secure backend. It sends the email code, then resets TOTP after the end user provides that code.

{% tabs %}
{% tab title="JavaScript (Axios)" %}

```javascript
import axios from 'axios';
import crypto from 'crypto';

const apiKey = process.env.TYLT_API_KEY;
const apiSecret = process.env.TYLT_API_SECRET;
const baseUrl = 'https://api.tylt.money';
const externalUserId = 'u-xyz';

if (!apiKey || !apiSecret) {
  throw new Error('Set TYLT_API_KEY and TYLT_API_SECRET.');
}

function createSignature(params) {
  return crypto
    .createHmac('sha256', apiSecret)
    .update(JSON.stringify(params))
    .digest('hex');
}

async function sendResetCode() {
  const queryParams = { externalUserId };
  const queryString = new URLSearchParams(queryParams).toString();
  const headers = {
    'x-tlp-apikey': apiKey,
    'x-tlp-signature': createSignature(queryParams)
  };

  const response = await axios.get(
    `${baseUrl}/whitelabel/auth/sendResetTotpOTP?${queryString}`,
    { headers }
  );

  console.log('Reset code sent:', response.data);
}

async function resetTotp(emailCode) {
  const requestBody = {
    externalUserId,
    emailCode,
    timestamp: Date.now(),
    nonce: crypto.randomUUID()
  };
  const rawBody = JSON.stringify(requestBody);
  const headers = {
    'Content-Type': 'application/json',
    'x-tlp-apikey': apiKey,
    'x-tlp-signature': createSignature(requestBody)
  };

  const response = await axios.post(
    `${baseUrl}/whitelabel/auth/resetTotp`,
    rawBody,
    { headers }
  );

  console.log('New TOTP credentials:', response.data.data);
  return response.data.data;
}

try {
  await sendResetCode();

  // Collect this code from the end user through your secure application flow.
  const emailCode = '123456';
  const { totpSecret, totpQrCodeString } = await resetTotp(emailCode);

  // Display totpQrCodeString as a QR code only to this end user.
  // Never write totpSecret or totpQrCodeString to logs.
} catch (error) {
  const response = error.response?.data ?? error.message;
  console.error('TOTP reset failed:', response);
}
```

{% endtab %}
{% endtabs %}

Replace `externalUserId` with the relevant end-user identifier. Replace `emailCode` only after the end user supplies the emailed code.

#### Successful response

```http
200 OK
```

```json
{
  "msg": "TOTP reset successful.",
  "data": {
    "totpSecret": "JBSWY3DPEHPK3PXP",
    "totpQrCodeString": "otpauth://totp/Tylt Money?secret=JBSWY3DPEHPK3PXP"
  }
}
```

#### Response fields

| Field              | Type   | Description                                 |
| ------------------ | ------ | ------------------------------------------- |
| `totpSecret`       | string | The new base32 TOTP secret.                 |
| `totpQrCodeString` | string | An `otpauth://` URI for QR-code enrollment. |

Treat both values as credentials. They are returned only by this reset request.

Securely deliver the QR code or secret to the relevant end user. Do not expose either value in logs or analytics.

#### Errors

| HTTP status | `msg`                                                              | Cause                                                                   |
| ----------- | ------------------------------------------------------------------ | ----------------------------------------------------------------------- |
| `401`       | `End user not resolved.`                                           | The middleware did not resolve an end user.                             |
| `400`       | `Parameter emailCode is mandatory.`                                | `emailCode` is missing.                                                 |
| `400`       | `Access Denied! OTP does not match!`                               | The code is incorrect or expired. A mismatch does not consume the code. |
| `400`       | `Unable to reset TOTP. Contact Support.`                           | The user has no TOTP secret. A matching code is consumed.               |
| `400`       | `Parameters timestamp and nonce are mandatory for this operation.` | A replay-protection field is missing.                                   |
| `400`       | `timestamp is outside the allowed window.`                         | The timestamp is not within ±120 seconds.                               |
| `409`       | `Duplicate request detected (nonce already used).`                 | The nonce was already used.                                             |
| `500`       | `Failed to reset TOTP. Contact support.`                           | The database write failed.                                              |

> A matching email code is consumed immediately. This also applies if a later check fails. Request a new code if needed.

Successful resets are recorded in `whitelabel_audit_log`. The record includes the merchant ID, end-user ID, API-key authentication type, and client IP.

### Recovery flow

1. Call `GET /whitelabel/auth/sendResetTotpOTP` with an end-user identifier.
2. Collect the emailed code through the merchant's own user experience.
3. Call `POST /whitelabel/auth/resetTotp` with the code, timestamp, and nonce.
4. Display the returned QR code for new authenticator enrollment.

The merchant cannot create the end user's email code. The end user must provide it through the merchant's user experience.
