One-time passwords (OTPs)
Use One-Time Passwords (OTPs) to confirm that a customer controls a phone number
before you sign them in, change sensitive account data, or approve an action.
Inttegro generates and sends the time-limited code; your application should
continue only after verification returns a pass verdict.
The OTP transaction object
An OTP transaction represents one code from generation through delivery and verification. Initiation and lookup responses never expose the generated code: collect the code from the customer, then submit that value to the verify endpoint.
Properties
- Name
status- Type
- string
- Description
Current transaction status. Possible values are
canceled,expired,pending,pending_delivery,pending_verification, andverified.pending_deliverymeans delivery-attempt fields are not available yet;pending_verificationmeans the message was sent and the transaction is waiting for a verification attempt.
Non-success responses return a top-level error object. Use the HTTP status for
control flow, inspect error.code and error.fix_code when present, and keep
message and detail for display or diagnostics rather than parsing their prose.
Some OTP failures currently omit a stable code, so clients must not require one.
Initiate OTP transaction
Generate a one-time password, deliver it to an international phone number by SMS, and return a typed transaction ID for verification.
Required attributes
Optional attributes
The fully rendered SMS, including the generated code and substituted service name, must be no longer than 120 characters. The transaction expires after its validity window even if delivery is delayed.
Response
Returns 200 when the transaction is created, 400 for malformed input,
401 when authorization fails, and 422 when the OTP request or delivery
could not be completed. With synchronous delivery, inspect
transmission.status: submitted means the message was accepted for
delivery, delivered means delivery was confirmed, and failed means the
delivery attempt failed.
Request
- cURL
- TypeScript
- Go
- Python
- PHP
- Ruby
- Java
- C#
curl https://api.inttegro.com/otp/initiate \
-H "Authorization: Bearer $INTTEGRO_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: otp-login-67890" \
-d '{
"message_template": "Your {service} code is {token}.",
"purpose": "transaction_confirm",
"recipient": "+233241234567",
"sender": "Acme",
"service_name": "Acme Bank",
"token_alphabet_type": "numeric",
"token_size": 6,
"validity_duration_in_minutes": 10
}'
import * as Inttegro from '@inttegro/inttegro-sdk'
const inttegro = new Inttegro.InttegroClient({
apiKey: process.env.INTTEGRO_API_KEY!,
})
const result = await inttegro.otp.initiate({
messageTemplate: "Your {service} code is {token}.",
purpose: "transaction_confirm",
recipient: "+233241234567",
sender: "Acme",
serviceName: "Acme Bank",
tokenAlphabetType: "numeric",
tokenSize: 6,
validityDurationInMinutes: 10,
})
package main
import (
"context"
"log"
"os"
inttegro "github.com/zebodotdev/inttegro-sdk-go/v4"
)
func main() {
ctx := context.Background()
client := inttegro.NewClient(os.Getenv("INTTEGRO_API_KEY"))
params := map[string]any{
"message_template": "Your {service} code is {token}.",
"purpose": "transaction_confirm",
"recipient": "+233241234567",
"sender": "Acme",
"service_name": "Acme Bank",
"token_alphabet_type": "numeric",
"token_size": 6,
"validity_duration_in_minutes": 10,
}
result, err := client.Otp.Initiate(ctx, params)
if err != nil {
log.Fatal(err)
}
_ = result
}
import os
import inttegro
client = inttegro.InttegroClient(api_key=os.environ["INTTEGRO_API_KEY"])
result = client.otp.initiate(inttegro.otp.InitiateRequest(
message_template="Your {service} code is {token}.",
purpose="transaction_confirm",
recipient="+233241234567",
sender="Acme",
service_name="Acme Bank",
token_alphabet_type="numeric",
token_size=6,
validity_duration_in_minutes=10,
))
<?php
use Inttegro\Client;
$client = new Client($_ENV['INTTEGRO_API_KEY']);
$result = $client->otp->initiate([
'message_template' => 'Your {service} code is {token}.',
'purpose' => 'transaction_confirm',
'recipient' => '+233241234567',
'sender' => 'Acme',
'service_name' => 'Acme Bank',
'token_alphabet_type' => 'numeric',
'token_size' => 6,
'validity_duration_in_minutes' => 10,
]);
require "inttegro"
client = Inttegro::Client.new(api_key: ENV.fetch("INTTEGRO_API_KEY"))
result = client.otp.initiate(
message_template: "Your {service} code is {token}.",
purpose: "transaction_confirm",
recipient: "+233241234567",
sender: "Acme",
service_name: "Acme Bank",
token_alphabet_type: "numeric",
token_size: 6,
validity_duration_in_minutes: 10
)
import com.inttegro.Client;
import com.inttegro.otp.InitiateOtpParams;
import com.inttegro.otp.OtpAlphabetType;
public class Example {
public static void main(String[] args) throws Exception {
var client = new Client(System.getenv("INTTEGRO_API_KEY"));
var params = InitiateOtpParams.builder()
.messageTemplate("Your {service} code is {token}.")
.purpose("transaction_confirm")
.recipient("+233241234567")
.sender("Acme")
.serviceName("Acme Bank")
.tokenAlphabetType(OtpAlphabetType.NUMERIC)
.tokenSize(6)
.validityDurationInMinutes(10)
.build();
var result = client.otp().initiate(params);
}
}
using Inttegro;
using var inttegro = new InttegroClient(
Environment.GetEnvironmentVariable("INTTEGRO_API_KEY")!
);
var result = await inttegro.Otp.InitiateAsync(new {
message_template = "Your {service} code is {token}.",
purpose = "transaction_confirm",
recipient = "+233241234567",
sender = "Acme",
service_name = "Acme Bank",
token_alphabet_type = "numeric",
token_size = 6,
validity_duration_in_minutes = 10,
});
Response
- Object
- JSON
TransactionResponse {
transaction: { … },
}
{
"transaction": {
"expires_at": "2026-08-28T10:10:00Z",
"full_message": "Your Acme Bank code is {token}.",
"id": "ot_<TRANSACTION_ID>",
"initiated_at": "2026-08-28T10:00:00Z",
"status": "pending_verification",
"transmission": { … }
}
}
Verify OTP
Record a verification attempt for a user-submitted token. An HTTP 200 response means the attempt was recorded; it does not mean the token matched. Authorize the user or action only when verification_attempt.result.verdict is pass.
Required attributes
The current transaction accepts at most five recorded attempts; after that, initiate a new transaction.
Response
Returns both transaction and verification_attempt. The attempt contains
attempted_at, id, presented_token, recipient, and result.
result.verdict is pass or fail; result.detail is omitted for a pass
and explains a failed match when available. Treat presented_token as
sensitive and exclude verification request and response bodies from logs.
Returns 200 for both matching and non-matching tokens, 400 for malformed
input, 401 when authorization fails, and 422 when the transaction is
expired, canceled, over its attempt limit, or otherwise cannot be verified.
Request
- cURL
- TypeScript
- Go
- Python
- PHP
- Ruby
- Java
- C#
response=$(curl https://api.inttegro.com/otp/verify \
-H "Authorization: Bearer $INTTEGRO_API_KEY" \
-H "Idempotency-Key: otp-verify-67890" \
-H "Content-Type: application/json" \
-d '{
"recipient": "+233241234567",
"token": "<OTP_CODE_FROM_USER>",
"transaction_id": "ot_<TRANSACTION_ID>"
}')
jq -e '.verification_attempt.result.verdict == "pass"' <<< "$response"
import * as Inttegro from '@inttegro/inttegro-sdk'
const inttegro = new Inttegro.InttegroClient({
apiKey: process.env.INTTEGRO_API_KEY!,
})
const result = await inttegro.otp.verify({
recipient: "+233241234567",
token: "<OTP_CODE_FROM_USER>",
transactionId: "ot_<TRANSACTION_ID>",
})
package main
import (
"context"
"log"
"os"
inttegro "github.com/zebodotdev/inttegro-sdk-go/v4"
)
func main() {
ctx := context.Background()
client := inttegro.NewClient(os.Getenv("INTTEGRO_API_KEY"))
params := map[string]any{
"recipient": "+233241234567",
"token": "<OTP_CODE_FROM_USER>",
"transaction_id": "ot_<TRANSACTION_ID>",
}
result, err := client.Otp.Verify(ctx, params)
if err != nil {
log.Fatal(err)
}
_ = result
}
import os
import inttegro
client = inttegro.InttegroClient(api_key=os.environ["INTTEGRO_API_KEY"])
result = client.otp.verify(inttegro.otp.VerifyRequest(
recipient="+233241234567",
token="<OTP_CODE_FROM_USER>",
transaction_id="ot_<TRANSACTION_ID>",
))
<?php
use Inttegro\Client;
$client = new Client($_ENV['INTTEGRO_API_KEY']);
$result = $client->otp->verify([
'recipient' => '+233241234567',
'token' => '<OTP_CODE_FROM_USER>',
'transaction_id' => 'ot_<TRANSACTION_ID>',
]);
require "inttegro"
client = Inttegro::Client.new(api_key: ENV.fetch("INTTEGRO_API_KEY"))
result = client.otp.verify(
recipient: "+233241234567",
token: "<OTP_CODE_FROM_USER>",
transaction_id: "ot_<TRANSACTION_ID>"
)
import com.inttegro.Client;
import com.inttegro.otp.VerifyOtpParams;
public class Example {
public static void main(String[] args) throws Exception {
var client = new Client(System.getenv("INTTEGRO_API_KEY"));
var params = VerifyOtpParams.builder()
.recipient("+233241234567")
.token("<OTP_CODE_FROM_USER>")
.transactionId("ot_<TRANSACTION_ID>")
.build();
var result = client.otp().verify(params);
}
}
using Inttegro;
using var inttegro = new InttegroClient(
Environment.GetEnvironmentVariable("INTTEGRO_API_KEY")!
);
var result = await inttegro.Otp.VerifyAsync(new {
recipient = "+233241234567",
token = "<OTP_CODE_FROM_USER>",
transaction_id = "ot_<TRANSACTION_ID>",
});
A failed match still returns HTTP 200 with a fail verdict. The API echoes
the submitted value as presented_token; the example uses a placeholder so
no real code is exposed.
Lookup OTP transaction
Retrieve details of an existing OTP transaction by its ID. Check transaction status, delivery state, and verification status.
Required attributes
Response
Returns the transaction without the generated code or verification-attempt
history. Optional cancellation and delivery-attempt fields are omitted when
they do not apply. Returns 200 when found, 400 for malformed input, 401
when authorization fails, and 422 when the transaction does not exist or
cannot be loaded.
Request
- cURL
- TypeScript
- Go
- Python
- PHP
- Ruby
- Java
- C#
curl https://api.inttegro.com/otp/lookup \
-H "Authorization: Bearer $INTTEGRO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"transaction_id": "ot_<TRANSACTION_ID>"
}'
import * as Inttegro from '@inttegro/inttegro-sdk'
const inttegro = new Inttegro.InttegroClient({
apiKey: process.env.INTTEGRO_API_KEY!,
})
const result = await inttegro.otp.lookup({
transactionId: "ot_<TRANSACTION_ID>",
})
package main
import (
"context"
"log"
"os"
inttegro "github.com/zebodotdev/inttegro-sdk-go/v4"
)
func main() {
ctx := context.Background()
client := inttegro.NewClient(os.Getenv("INTTEGRO_API_KEY"))
params := map[string]any{
"transaction_id": "ot_<TRANSACTION_ID>",
}
result, err := client.Otp.Lookup(ctx, params)
if err != nil {
log.Fatal(err)
}
_ = result
}
import os
import inttegro
client = inttegro.InttegroClient(api_key=os.environ["INTTEGRO_API_KEY"])
result = client.otp.lookup(inttegro.otp.LookupRequest(
transaction_id="ot_<TRANSACTION_ID>",
))
<?php
use Inttegro\Client;
$client = new Client($_ENV['INTTEGRO_API_KEY']);
$result = $client->otp->lookup([
'transaction_id' => 'ot_<TRANSACTION_ID>',
]);
require "inttegro"
client = Inttegro::Client.new(api_key: ENV.fetch("INTTEGRO_API_KEY"))
result = client.otp.lookup(
transaction_id: "ot_<TRANSACTION_ID>"
)
import com.inttegro.Client;
import com.inttegro.otp.LookupOtpParams;
public class Example {
public static void main(String[] args) throws Exception {
var client = new Client(System.getenv("INTTEGRO_API_KEY"));
var params = LookupOtpParams.builder()
.transactionId("ot_<TRANSACTION_ID>")
.build();
var result = client.otp().lookup(params);
}
}
using Inttegro;
using var inttegro = new InttegroClient(
Environment.GetEnvironmentVariable("INTTEGRO_API_KEY")!
);
var result = await inttegro.Otp.LookupAsync(new {
transaction_id = "ot_<TRANSACTION_ID>",
});
Response
- Object
- JSON
TransactionResponse {
transaction: { … },
}
{
"transaction": {
"expires_at": "2026-08-28T10:10:00Z",
"full_message": "Your Acme Bank code is {token}.",
"id": "ot_<TRANSACTION_ID>",
"initiated_at": "2026-08-28T10:00:00Z",
"status": "pending_verification",
"transmission": { … }
}
}
Related resources
- Create customer - Associate verified contact details with a customer record.
- Send Chime - Send transactional SMS and email notifications after verification.