# Transactional Time-based One Time Passwords

Use this feature if you need to tie a one-time password with transaction data. It's specifically useful to comply with [PSD2 Dynamic Linking](https://www.twilio.com/blog/dynamic-linking-psd2) to fulfill Strong Customer Authentication Requirement.

To enable this feature you need to enable it in your Application's settings in the [Twilio Console](https://www.twilio.com/console/authy/applications):

![Transactional TOTP feature enabled for QR code scanning in Authy app.](https://docs-resources.prod.twilio.com/6c88b0cec9450f2c66d6d03a0ca12636e57e60275063d8f96ba3380d525c8017.png)

Once this is enabled, end users will have the option to Scan a QR code from your application view in the Authy App.

![Instructions to scan TOTP QR code for approving a 1000 Euro transaction to John Doe.](https://docs-resources.prod.twilio.com/cf7b5e50ab5c853560ede7002c5dadc1bc82340c444383e29d6ee4e26b1ea737.png)

> \[!NOTE]
>
> This feature is only supported in the following versions of the Authy App. End users will have to update their mobile applications to the following versions:
>
> * iOS 22.3+
> * Android 23.7+

## Create a Transactional QR code

First, create a transaction string with the following format. This will be encoded in the QR code.

```bash
txotp://totp?message=msg&details[key1]=value1&hidden_details[key1]=value1
```

`message`, `details`, and `hidden_details` are mixed with a seed to generate a unique TOTP. The same exact parameters are required to successfully verify the token. Order of parameters is not important but details will be displayed in the same order they are sent.

Parameters should be URL encoded.

### Parameters \[#create-transactional-qr-code-parameters]

| Name                             | Description                                                                                                |
| -------------------------------- | ---------------------------------------------------------------------------------------------------------- |
| `message` String                 | Short description about the transaction                                                                    |
| `details` Hash                   | Dictionary containing any details related to the transaction you want the end user to see.                 |
| `hidden_details` Hash (optional) | Dictionary containing any details related to the transaction you don't necessary want the end user to see. |

```text title="Example transaction string"
txotp://totp?message=Approve+money+transaction&details[Amount]=1000+Euros&details[To]=John+Doe&details[Destination+Account]=29385&details[Source+Account]=98381&details[Reason]=transfer+money&hidden_details[Transaction+ID]=T2293
```

> \[!NOTE]
>
> The recommended length for a transaction string is under 300 characters. It is
> not recommended to exceed 600 characters. Longer transaction strings create
> more complicated QR codes and cause slower scanning, especially in older
> smartphones. This example's length is 228 characters.

### Generate the QR Code from the transaction string

Note: we do not recommend storing the QR code since it can potentially leak sensitive data. Instead, we recommend:

1. Sending the QR code as a base64 encoded image in your HTML
2. Generating the QR code from your application with a library like [github.com/skip2/go-qrcode](https://urldefense.proofpoint.com/v2/url?u=https-3A__www.google.com_url-3Fq-3Dhttp-3A__github.com_skip2_go-2Dqrcode-26sa-3DD-26ust-3D1552925750139000-26usg-3DAFQjCNFHnLhh736rQauZ-2DIRPte2dsmnWfA\&d=DwMFaQ\&c=x_Y1Lz9GyeGp2OvBCa_eow\&r=leI7Dp63vTbpZqkO-L0qk-g5DpNpbi9MdGHfE4q6-8g\&m=4J53eUnsCQ5lIRQZ-J_8vrnRzQiWtC5x14d2fO4im08\&s=E3bw0NByrmm_WZrSyYdAL2EpsChF92NtD1x8uuXjscA\&e=)

After the QR code is generated display it so your user can scan it with the Authy App. Here is a sample QR code generated from the example transaction string.

![QR code for transactional TOTP setup.](https://docs-resources.prod.twilio.com/e9c0c6dd7a4b5b9d0ccea957ae0a047ea24a152e667a46780d62c0bced9044af.png)

Your QR code may not look exactly like the example above when you recreate it. Test if the example works by scanning your QR code with the Authy app on your smartphone.

## Verify a Transactional One-Time Password

To verify a transactional one-time password, pass in the user provided `token`, the user `authy_id` and the transaction `message`, `details` and `hidden_details`. Twilio will use HTTP status codes for the response.

```bash
GET https://api.authy.com/protected/{FORMAT}/verify/{TOKEN}/{AUTHY_ID}
```

### URL

| Name              | Description                                                              |
| ----------------- | ------------------------------------------------------------------------ |
| `FORMAT` String   | The format to expect back from the REST API call. `json` or `xml`.       |
| `TOKEN` Integer   | The 6-8 digit transactional TOTP you are verifying.                      |
| `AUTHY_ID` String | The Authy ID for the [user](/docs/authy/api/users) validating the token. |

### Parameters

| Name                             | Description                                                                                                |
| -------------------------------- | ---------------------------------------------------------------------------------------------------------- |
| `message` String                 | Short description about the transaction                                                                    |
| `details` Hash                   | Dictionary containing any details related to the transaction you want the end user to see.                 |
| `hidden_details` Hash (optional) | Dictionary containing any details related to the transaction you don't necessary want the end user to see. |

### HTTP Status Codes

| Code | Message                                                                                                                                           |
| ---- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| 200  | "Token is valid." Until you successfully verify a token for a *new* user, this will return `200` (see Note)\*.                                    |
| 401  | "Token is invalid." Invalid token. If you wish to verify the token anyway, pass `force=true` (see Note)\*.                                        |
| 401  | "The param details can not have empty values." For example a request with `?details[Name]=&details[Surname]=Doe` will fail.                       |
| 401  | "The param hidden details can not have empty values." For example a request with `?hidden_details[ID]=&hidden_details[Account]=690239` will fail. |

### Response

| Name             | Description                                                                                                                                                                                                                                                                                                                 |
| ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `token` String   | Either `"is valid"` or `"is invalid"` (🏢 not PII)                                                                                                                                                                                                                                                                          |
| `success` String | `"true"` if the code was valid. **Note**: This field is a String and not a Boolean. (🏢 not PII)                                                                                                                                                                                                                            |
| `device` Object  | An object including some details about the device used to get or generate the token. The fields included in the device object are: city, region, country, ip, registration\_city, registration\_region, registration\_country, registration\_ip, registration\_date, os\_type, last\_account\_recovery\_at and id. (📇 PII) |

> \[!WARNING]
>
> Every new Authy [user](/docs/authy/api/users) must complete a verification
> before subsequent authentications can be done. You can also [force
> verification of unregistered users](/docs/authy/api/one-time-passwords#force-one-time-password-validation-for-unregistered-user).

Verify Transactional TOTP

```py
# Download the SDK from https://github.com/twilio/authy-python
from authy.api import AuthyApiClient

# Your API key from twilio.com/console/authy/applications
# DANGER! This is insecure. See https://twil.io/secure
authy_api = AuthyApiClient('api_key')

options = {
    "message": "Approve money transaction",
    "details[Amount]": "1000 Euros",
    "details[To]": "John Doe",
    "details[Destination Account]": 29385,
    "details[Source Account]": 98381,
    "details[Reason]": "transfer money",
    "hidden_details[Transaction ID]": "T2293"
}

verification = authy_api.tokens.verify(
    authy_id,
    token=1685105,
    options=options)

print(verification.ok())
```

```cs
using System;
using System.Net.Http;
using System.Web;
using System.Collections.Generic;


class Program
{
    // verify txtotp
    static void Main(string[] args)
    {
        // Your API key from twilio.com/console/authy/applications
        // DANGER! This is insecure. See https://twil.io/secure
        var AuthyAPIKey = "your_api_key";

        using (var client = new HttpClient())
        {
            client.DefaultRequestHeaders.Add("X-Authy-API-Key", AuthyAPIKey);

            var query = HttpUtility.ParseQueryString(string.Empty);
            query["message"] = "Approve money transaction";
            query["details[Amount]"] = "1000 Euros";
            query["details[To]"] = "John Doe";
            query["details[Destination Account]"] = 29385;
            query["details[Source Account]"] = 98381;
            query["details[Reason]"] = "transfer money";
            query["hidden_details[Transaction ID]"] = "T2293";

            var authy_id = 1234;
            var token = 1234567;

            HttpResponseMessage response = client.GetAsync(
                $"https://api.authy.com/protected/json/verify/{token}/{authy_id}?{query.ToString()}").Result;

            HttpContent responseContent = response.Content;
            Console.WriteLine(responseContent.ReadAsStringAsync().Result);
        }
    }
}
```

```java
// Install the Authy Java SDK from github.com/twilio/authy-java

import com.authy.AuthyApiClient;
import com.authy.api.*;

public class Example {
    // Your API key from twilio.com/console/authy/applications
    // DANGER! This is insecure. See https://twil.io/secure
    public static final String API_KEY = "your_api_key";

    public static void main(String[] args) {
        AuthyApiClient client = new AuthyApiClient(API_KEY);

        Tokens tokens = client.getTokens();

        Map<String, String> options = new HashMap<String, String>();
        options.put("message", "Approve money transaction");
        options.put("details[Amount]", "1000 Euros");
        options.put("details[To]", "John Doe");
        options.put("details[Destination Account]", 29385);
        options.put("details[Source Account]", 98381);
        options.put("details[Reason]", "transfer money");
        options.put("hidden_details[Transaction ID]", "T2293");

        Token response = tokens.verify(authyId, "1297431", options);

        if (response.isOk()) {
            System.out.println(response.toMap());
        } else {
            System.out.println(response.getError());
        }
    }
}
```

```php
<?php

// Download the SDK from https://github.com/twilio/authy-php
// Update the path below to your autoload.php,
// see https://getcomposer.org/doc/01-basic-usage.md
require_once '/path/to/vendor/autoload.php';

// Your API key from twilio.com/console/authy/applications
// DANGER! This is insecure. See https://twil.io/secure
$authy_api = new Authy\AuthyApi('api_key');

$options = array(
  "message" => "Approve money transaction",
  "details[Amount]" => "1000 Euros",
  "details[To]" => "John Doe",
  "details[Destination Account]" => 29385,
  "details[Source Account]" => 98381,
  "details[Reason]" => "transfer money",
  "hidden_details[Transaction ID]" => "T2293"
);

$token_entered_by_user = '1234567';
$verification = $authy_api->verifyToken($authy_id, $token_entered_by_user, $options);

if ($verification->ok()) {
  // correct token and options
}
```

```bash
curl 'https://api.authy.com/protected/json/verify/{TOKEN}/{AUTHY_ID}?message=Approve+money+transaction&details[To]=John+Doe&details[Amount]=1000+Euros&details[Destination+Account]=29385&details[Source+Account]=98381&details[Reason]=transfer+money&hidden_details[Transaction+ID]=T2293' \
  -H 'X-Authy-API-Key: d57d919d11e6b221c9bf6f7c882028f9'
```

```json
{
  "message":"Token is valid.",
  "token":"is valid",
  "success":"true",
  "device":{
    "city":"Brooklyn",
    "country":"United States",
    "ip":"192.168.0.1",
    "region":"New York",
    "registration_city":"Medellín",
    "registration_country":"Colombia",
    "registration_ip":"192.168.0.3",
    "registration_method":"sms",
    "registration_region":"Antioquia",
    "os_type":"ios",
    "last_account_recovery_at":1498837117,
    "id":4854321,
    "registration_date":1551300946,
    "last_sync_date":1551301762
  }
}
```

An invalid token will return the following error message:

```json
{
  "message": "Token is invalid",
  "token": "is invalid",
  "success": false,
  "errors": {
    "message": "Token is invalid"
  },
  "error_code": "60020"
}
```
