One-Time Passwords (OTPs)

One-Time Passwords (OTPs) enable verification flows where you need to confirm control of a phone number or authorize a sensitive action. Generate a time-limited token, deliver it by SMS, and verify the recipient's submission.

The OTP transaction object

An OTP transaction represents a complete verification session from token generation through delivery and verification. Each transaction tracks delivery status, expiration time, and verification attempts.

Properties

  • Name
    cancel_reason
    Type
    string|null
    Description

    Human-readable explanation for why this transaction was canceled. Present only when status is canceled.

  • Name
    canceled_at
    Type
    timestamp|null
    Description

    When this transaction was explicitly canceled. Present only when status is canceled.

  • Name
    expires_at
    Type
    timestamp
    Description

    When this token expires and can no longer be verified.

  • Name
    full_message
    Type
    string
    Description

    The message template with the service name substituted and the token placeholder preserved as {token}. The token value is not included in this field.

  • Name
    id
    Type
    string
    Description

    Unique identifier for this transaction—use it to verify tokens and lookup transaction status.

  • Name
    initiated_at
    Type
    timestamp
    Description

    When this transaction was created and token generation began.

  • Name
    status
    Type
    string
    Description

    Current transaction status. Possible values are canceled, expired, pending, pending_delivery, pending_verification, and verified. pending_delivery means transmission details are not available yet; pending_verification means the message was sent and the transaction is waiting for a verification attempt.

  • transmissionobjectDelivery details for the OTP message. Present only after delivery attempt begins.Click or tap to expand
    • Name
      recipient
      Type
      string
      Description
      International phone number that received the SMS.
    • Name
      sender_id
      Type
      string
      Description
      Sender identifier shown to the recipient—alphanumeric sender ID or short code.
    • Name
      sent_at
      Type
      timestamp|null
      Description
      When the message was sent. Present only after delivery attempt.
    • Name
      sent_via
      Type
      string|null
      Description
      Delivery mechanism. OTP transactions currently use sms. Present only after the delivery attempt starts.
    • Name
      status
      Type
      string|null
      Description
      Delivery status: submitted, delivered, or failed. Present only after delivery attempt.

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. Use an idempotency key so a retry does not send a second token.

Required attributes

  • Name
    recipient
    Type
    string
    Description

    International phone number that will receive the SMS, such as +233241234567.

  • Name
    service_name
    Type
    string
    Description

    Service or application name substituted for {service} in the message template. Length: 2-20 characters.

  • Name
    token_size
    Type
    integer
    Description

    Generated token length. The current API requires a value from 5 through 10, inclusive.

Optional attributes

  • Name
    async_delivery
    Type
    boolean
    Description

    When false or omitted, the request waits for the initial transmission attempt. When true, the transaction can be returned before transmission details are available, with status pending_delivery.

  • Name
    message_template
    Type
    string
    Description

    Custom SMS template. It must include {token} and can include {service}. If omitted, the API uses its configured template.

  • Name
    purpose
    Type
    string
    Description

    Purpose of this OTP for your own analytics and filtering, such as login or transaction_confirm.

  • request_metaobjectRequest-specific controls that do not change the OTP being initiated.Click or tap to expand
    • Name
      idempotency_key
      Type
      string
      Description
      Stable key to prevent duplicate OTP sends during retries. You may also send the same value in the Idempotency-Key header. If omitted, Commerce generates a UUIDv7 key for this request.
  • Name
    sender
    Type
    string
    Description

    Sender identifier shown to the recipient. If supplied, its trimmed length must be 3-12 characters. If omitted, the API uses its configured sender.

  • Name
    token_alphabet
    Type
    string
    Description

    Custom alphabet for token generation. Pass a string of characters to use (e.g., 0123456789ABCDEF for hexadecimal tokens). Mutually exclusive with token_alphabet_type—use one or the other, not both.

  • Name
    token_alphabet_type
    Type
    enum
    Description

    Predefined alphabet type for token generation. Supported values are alpha, alphanumeric, and numeric. If neither alphabet field is supplied, the default alphabet is alphanumeric. Mutually exclusive with token_alphabet.

  • Name
    validity_duration_in_minutes
    Type
    integer
    Description

    How long the token remains valid. Accepted values are 3 through 10,080 minutes (7 days), inclusive. Defaults to 10 minutes.

Request

POST
/otp/initiate
curl https://api.zebo.dev/otp/initiate \
  -H "Authorization: Bearer $COMMERCE_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
  }'
{
  "transaction": {
    "expires_at": "2026-08-28T10:10:00Z",
    "full_message": "Your Acme Bank code is {token}. Valid for 10 minutes.",
    "id": "ot_<TRANSACTION_ID>",
    "initiated_at": "2026-08-28T10:00:00Z",
    "status": "pending_verification",
    "transmission": {
      "recipient": "+233241234567",
      "sender_id": "Acme",
      "sent_at": "2026-08-28T10:00:01Z",
      "sent_via": "sms",
      "status": "sent"
    }
  }
}

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

  • Name
    recipient
    Type
    string
    Description

    Phone number that received the OTP. Must match the recipient from the initiation request.

  • Name
    token
    Type
    string
    Description

    The OTP token submitted by the user. This is the code they received via SMS. Cannot be blank or whitespace-only.

  • Name
    transaction_id
    Type
    string
    Description

    Typed ID returned from /otp/initiate, in the form ot_<TRANSACTION_ID>.

Request

POST
/otp/verify
response=$(curl https://api.zebo.dev/otp/verify \
  -H "Authorization: Bearer $COMMERCE_API_KEY" \
  -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"

A failed match still returns HTTP 200 with a fail verdict. This response excerpt intentionally omits the submitted token; do not log verification request or response bodies.

{
  "verification_attempt": {
    "attempted_at": "2026-08-28T10:03:00Z",
    "id": "ov_<ATTEMPT_ID>",
    "recipient": "+233241234567",
    "result": {
      "detail": "token_mismatch",
      "verdict": "fail"
    }
  },
  "transaction": {
    "expires_at": "2026-08-28T10:10:00Z",
    "full_message": "Your Acme Bank code is {token}. Valid for 10 minutes.",
    "id": "ot_<TRANSACTION_ID>",
    "initiated_at": "2026-08-28T10:00:00Z",
    "status": "pending_verification",
    "transmission": {
      "recipient": "+233241234567",
      "sender_id": "Acme",
      "sent_at": "2026-08-28T10:00:01Z",
      "sent_via": "sms",
      "status": "sent"
    }
  }
}

Lookup OTP transaction

Retrieve details of an existing OTP transaction by its ID. Check transaction status, delivery state, and verification status.

Required attributes

  • Name
    transaction_id
    Type
    string
    Description

    Typed OTP transaction ID to look up. It must belong to your application.

Request

POST
/otp/lookup
curl https://api.zebo.dev/otp/lookup \
  -H "Authorization: Bearer $COMMERCE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "transaction_id": "ot_<TRANSACTION_ID>"
  }'
{
  "transaction": {
    "expires_at": "2026-08-28T10:10:00Z",
    "full_message": "Your Acme Bank code is {token}. Valid for 10 minutes.",
    "id": "ot_<TRANSACTION_ID>",
    "initiated_at": "2026-08-28T10:00:00Z",
    "status": "pending_verification",
    "transmission": {
      "recipient": "+233241234567",
      "sender_id": "Acme",
      "sent_at": "2026-08-28T10:00:01Z",
      "sent_via": "sms",
      "status": "sent"
    }
  }
}

Cancel OTP transaction

Cancel an active OTP transaction to prevent further verification attempts. Cancel when a user requests a new OTP, when suspicious activity is detected, or when the authentication flow is abandoned. Canceled transactions cannot be verified even if the token hasn't expired.

Required attributes

  • Name
    transaction_id
    Type
    string
    Description

    Typed OTP transaction ID to cancel.

Optional attributes

  • Name
    reason
    Type
    string
    Description

    Human-readable cancellation reason. If omitted, the API records a generic reason.

Request

POST
/otp/cancel
curl https://api.zebo.dev/otp/cancel \
  -H "Authorization: Bearer $COMMERCE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "reason": "user_requested_new_code",
    "transaction_id": "ot_<TRANSACTION_ID>"
  }'
{
  "transaction": {
    "cancel_reason": "user_requested_new_code",
    "canceled_at": "2026-08-28T10:02:00Z",
    "created_at": "2026-08-28T10:00:00Z",
    "delivered_at": "2026-08-28T10:00:01Z",
    "full_message": "Your Acme Bank code is {token}.",
    "id": "ot_<TRANSACTION_ID>",
    "mechanism": "sms",
    "recipient": "+233241234567",
    "sender": "Acme",
    "status": "canceled",
    "verifiable_until": "2026-08-28T10:10:00Z"
  }
}

Was this page helpful?