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

# OTP Authentication

> Send verification codes via WhatsApp Authentication templates

WhatsApp Authentication templates provide a secure, high-delivery-rate channel for sending one-time passwords (OTP) and verification codes. This guide covers creating, submitting, and sending OTP messages.

## Overview

Authentication templates are purpose-built for verification codes:

* **High delivery rates** - WhatsApp messages have 98%+ open rates
* **Instant delivery** - Codes arrive in seconds
* **User-friendly** - Copy button or auto-fill on Android
* **Cost-effective** - Lowest messaging rates of all template categories
* **Secure** - End-to-end encrypted delivery

## Prerequisites

Before creating OTP templates, ensure you have:

<CardGroup cols={2}>
  <Card title="Meta Business Verification" icon="check" href="/guides/whatsapp/connect-whatsapp">
    Your Meta Business account must be verified
  </Card>

  <Card title="WhatsApp Connected" icon="whatsapp" href="/guides/whatsapp/connect-whatsapp">
    A sender with WhatsApp Business Account configured
  </Card>

  <Card title="Payment Method" icon="credit-card">
    Valid payment method configured in Meta Business Manager
  </Card>

  <Card title="API Key" icon="key" href="/authentication">
    A Zavu API key for your project
  </Card>
</CardGroup>

## Step 1: Create OTP Template

### Via Dashboard

1. Navigate to **Senders** in your dashboard
2. Select the sender with WhatsApp connected
3. Go to the **Templates** tab
4. Click **Create Template**
5. Configure the template:
   * **Name**: Use a descriptive name like `otp_verification`
   * **Channel**: Select WhatsApp only
   * **Category**: Select **AUTHENTICATION**
   * **Language**: Choose your language (e.g., `en`)
6. Configure the OTP button:
   * **Button Type**: Choose `Copy Code` or `One-Tap` (Android only)
7. Optional settings:
   * Enable **Security Recommendation** to add "Do not share this code"
   * Set **Code Expiration** (1-90 minutes)
8. Click **Create**

### Via API

<CodeGroup>
  ```typescript TypeScript theme={null}
  import Zavudev from '@zavudev/sdk';

  const zavu = new Zavudev({
    apiKey: process.env["ZAVUDEV_API_KEY"],
  });

  const template = await zavu.templates.create({
    name: 'otp_verification',
    language: 'en',
    body: '{{1}} is your verification code.',
    category: 'AUTHENTICATION',
    buttons: [{
      type: 'otp',
      text: 'Copy code',
      otpType: 'COPY_CODE'
    }],
    addSecurityRecommendation: true,
    codeExpirationMinutes: 10
  });

  console.log('Template ID:', template.id);
  console.log('Status:', template.status); // "draft"
  ```

  ```python Python theme={null}
  import os
  from zavudev import Zavudev

  zavu = Zavudev(
      api_key=os.environ.get("ZAVUDEV_API_KEY"),
  )

  template = zavu.templates.create(
      name="otp_verification",
      language="en",
      body="{{1}} is your verification code.",
      category="AUTHENTICATION",
      buttons=[{
          "type": "otp",
          "text": "Copy code",
          "otp_type": "COPY_CODE"
      }],
      add_security_recommendation=True,
      code_expiration_minutes=10
  )

  print("Template ID:", template.id)
  print("Status:", template.status)  # "draft"
  ```

  ```ruby Ruby theme={null}
  template = client.templates.create(
    name: "otp_verification",
    language: "en",
    body: "{{1}} is your verification code.",
    category: "AUTHENTICATION",
    buttons: [{
      type: "otp",
      text: "Copy code",
      otp_type: "COPY_CODE"
    }],
    add_security_recommendation: true,
    code_expiration_minutes: 10
  )

  puts "Template ID: #{template.id}"
  puts "Status: #{template.status}" # "draft"
  ```

  ```go Go theme={null}
  template, err := client.Templates.Create(context.TODO(), zavudev.TemplateCreateParams{
      Name:     zavudev.String("otp_verification"),
      Language: zavudev.String("en"),
      Body:     zavudev.String("{{1}} is your verification code."),
      WhatsappCategory: zavudev.String("AUTHENTICATION"),
      Buttons: []zavudev.TemplateButtonParams{
          {
              Type:    zavudev.String("otp"),
              Text:    zavudev.String("Copy code"),
              OTPType: zavudev.String("COPY_CODE"),
          },
      },
      AddSecurityRecommendation: zavudev.Bool(true),
      CodeExpirationMinutes:     zavudev.Int(10),
  })

  fmt.Println("Template ID:", template.ID)
  fmt.Println("Status:", template.Status) // "draft"
  ```

  ```php PHP theme={null}
  $template = $client->templates->create([
      'name' => 'otp_verification',
      'language' => 'en',
      'body' => '{{1}} is your verification code.',
      'category' => 'AUTHENTICATION',
      'buttons' => [[
          'type' => 'otp',
          'text' => 'Copy code',
          'otpType' => 'COPY_CODE',
      ]],
      'addSecurityRecommendation' => true,
      'codeExpirationMinutes' => 10,
  ]);

  echo 'Template ID: ' . $template->id . "\n";
  echo 'Status: ' . $template->status . "\n"; // "draft"
  ```

  ```bash cURL theme={null}
  curl -X POST https://api.zavu.dev/v1/templates \
    -H "Authorization: Bearer $ZAVU_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "name": "otp_verification",
      "language": "en",
      "body": "{{1}} is your verification code.",
      "category": "AUTHENTICATION",
      "buttons": [{
        "type": "otp",
        "text": "Copy code",
        "otpType": "COPY_CODE"
      }],
      "addSecurityRecommendation": true,
      "codeExpirationMinutes": 10
    }'
  ```
</CodeGroup>

<Info>
  Meta auto-generates the message body for authentication templates. The format is always: `{{1}} is your verification code.` followed by optional security text and expiration.
</Info>

## Step 2: Submit for Meta Approval

Authentication templates typically receive fast approval (within hours).

### Via Dashboard

1. Go to **Senders** > Select sender > **Templates** tab
2. Find your template with "Draft" status
3. Click the **...** menu and select **Submit for Approval**

### Via API

<CodeGroup>
  ```typescript TypeScript theme={null}
  await zavu.templates.submit({
    templateId: template.id,
    senderId: 'sender_abc123'
  });

  // Check status
  const updated = await zavu.templates.get({
    templateId: template.id
  });
  console.log('Status:', updated.status); // "pending" -> "approved"
  ```

  ```python Python theme={null}
  zavu.templates.submit(
      template_id=template.id,
      sender_id="sender_abc123"
  )

  # Check status
  updated = zavu.templates.get(template_id=template.id)
  print("Status:", updated.status)  # "pending" -> "approved"
  ```

  ```ruby Ruby theme={null}
  client.templates.submit(
    template_id: template.id,
    sender_id: "sender_abc123"
  )

  # Check status
  updated = client.templates.get(template_id: template.id)
  puts "Status: #{updated.status}" # "pending" -> "approved"
  ```

  ```go Go theme={null}
  _, err = client.Templates.Submit(context.TODO(), template.ID, zavudev.TemplateSubmitParams{
      SenderID: zavudev.String("sender_abc123"),
  })

  // Check status
  updated, err := client.Templates.Get(context.TODO(), template.ID)
  fmt.Println("Status:", updated.Status) // "pending" -> "approved"
  ```

  ```php PHP theme={null}
  $client->templates->submit($template->id, [
      'senderId' => 'sender_abc123',
  ]);

  // Check status
  $updated = $client->templates->get($template->id);
  echo 'Status: ' . $updated->status . "\n"; // "pending" -> "approved"
  ```

  ```bash cURL theme={null}
  curl -X POST https://api.zavu.dev/v1/templates/tmpl_abc123/submit \
    -H "Authorization: Bearer $ZAVU_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "senderId": "sender_abc123"
    }'
  ```
</CodeGroup>

### Expected Approval Time

| Template Type  | Typical Approval Time |
| -------------- | --------------------- |
| AUTHENTICATION | 1-4 hours             |
| UTILITY        | 24-48 hours           |
| MARKETING      | 24-72 hours           |

<Tip>
  Authentication templates are prioritized by Meta and usually approved within hours. If approval takes longer, ensure your Meta Business is fully verified.
</Tip>

## Step 3: Send OTP Messages

Once approved, send verification codes to users:

<CodeGroup>
  ```typescript TypeScript theme={null}
  import Zavudev from '@zavudev/sdk';

  const zavu = new Zavudev({
    apiKey: process.env["ZAVUDEV_API_KEY"],
  });

  // Generate your OTP code
  const otpCode = '123456';

  const message = await zavu.messages.send({
    to: '+14155551234',
    messageType: 'template',
    content: {
      templateId: 'tmpl_abc123',
      templateVariables: {
        '1': otpCode  // The OTP code
      }
    }
  });

  console.log('Message ID:', message.id);
  console.log('Status:', message.status);
  ```

  ```python Python theme={null}
  import os
  from zavudev import Zavudev

  zavu = Zavudev(
      api_key=os.environ.get("ZAVUDEV_API_KEY"),
  )

  # Generate your OTP code
  otp_code = "123456"

  message = zavu.messages.send(
      to="+14155551234",
      message_type="template",
      content={
          "template_id": "tmpl_abc123",
          "template_variables": {
              "1": otp_code  # The OTP code
          }
      }
  )

  print("Message ID:", message.id)
  print("Status:", message.status)
  ```

  ```ruby Ruby theme={null}
  # Generate your OTP code
  otp_code = "123456"

  message = client.messages.send(
    to: "+14155551234",
    message_type: "template",
    content: {
      template_id: "tmpl_abc123",
      template_variables: {
        "1" => otp_code  # The OTP code
      }
    }
  )

  puts "Message ID: #{message.id}"
  puts "Status: #{message.status}"
  ```

  ```go Go theme={null}
  // Generate your OTP code
  otpCode := "123456"

  message, err := client.Messages.Send(context.TODO(), zavudev.MessageSendParams{
      To:          zavudev.String("+14155551234"),
      MessageType: zavudev.String("template"),
      Content: &zavudev.MessageContentParams{
          TemplateID: zavudev.String("tmpl_abc123"),
          TemplateVariables: map[string]string{
              "1": otpCode, // The OTP code
          },
      },
  })

  fmt.Println("Message ID:", message.ID)
  fmt.Println("Status:", message.Status)
  ```

  ```php PHP theme={null}
  // Generate your OTP code
  $otpCode = '123456';

  $message = $client->messages->send([
      'to' => '+14155551234',
      'messageType' => 'template',
      'content' => [
          'templateId' => 'tmpl_abc123',
          'templateVariables' => [
              '1' => $otpCode, // The OTP code
          ],
      ],
  ]);

  echo 'Message ID: ' . $message->id . "\n";
  echo 'Status: ' . $message->status . "\n";
  ```

  ```bash cURL theme={null}
  curl -X POST https://api.zavu.dev/v1/messages \
    -H "Authorization: Bearer $ZAVU_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "to": "+14155551234",
      "messageType": "template",
      "content": {
        "templateId": "tmpl_abc123",
        "templateVariables": {
          "1": "123456"
        }
      }
    }'
  ```
</CodeGroup>

### What the User Sees

The user receives a message like:

```
123456 is your verification code.

For your security, do not share this code.

This code expires in 10 minutes.

[Copy code]
```

## OTP Button Types

### COPY\_CODE (Recommended)

The **Copy Code** button allows users to tap and copy the code to their clipboard.

```typescript theme={null}
buttons: [{
  type: 'otp',
  text: 'Copy code',
  otpType: 'COPY_CODE'
}]
```

**Benefits:**

* Works on all platforms (iOS, Android, Web)
* Simple user experience
* No additional configuration required

### ONE\_TAP (Android Only)

The **One-Tap** button enables automatic code filling in your Android app.

```typescript theme={null}
buttons: [{
  type: 'otp',
  text: 'Copy code',
  otpType: 'ONE_TAP',
  packageName: 'com.yourcompany.app',
  signatureHash: 'K8a+W1234...'
}]
```

**Requirements:**

* Android app only
* Must provide `packageName` (Android package name)
* Must provide `signatureHash` (Android app signature hash)

**How to get your signature hash:**

1. In your Android app, use the SMS Retriever API helper:

```java theme={null}
AppSignatureHelper helper = new AppSignatureHelper(context);
ArrayList<String> signatures = helper.getAppSignatures();
// Use the first signature
String signatureHash = signatures.get(0);
```

2. The hash is an 11-character string like `K8a+W1234ab`

<Warning>
  One-Tap auto-fill only works on Android. iOS users will see a standard copy button. Always ensure your flow works without auto-fill as a fallback.
</Warning>

## Template Options

### Security Recommendation

Add a security disclaimer to your OTP message:

```typescript theme={null}
addSecurityRecommendation: true
```

This adds: *"For your security, do not share this code."*

### Code Expiration

Display when the code expires:

```typescript theme={null}
codeExpirationMinutes: 10  // 1-90 minutes
```

This adds: *"This code expires in 10 minutes."*

## Best Practices

<CardGroup cols={2}>
  <Card title="Use Short Expiration" icon="clock">
    Set expiration to 5-10 minutes for security. Shorter is better.
  </Card>

  <Card title="Enable Security Warning" icon="shield">
    Always enable `addSecurityRecommendation` to protect users from phishing.
  </Card>

  <Card title="Generate Secure Codes" icon="lock">
    Use cryptographically secure random number generators for OTP codes.
  </Card>

  <Card title="Rate Limit Requests" icon="gauge">
    Limit OTP requests per user to prevent abuse (e.g., 5 per hour).
  </Card>
</CardGroup>

### OTP Code Guidelines

* Use 6-digit numeric codes for balance of security and usability
* Generate codes using secure random functions
* Store codes with expiration timestamps
* Invalidate codes after successful verification
* Limit verification attempts (e.g., 3 tries per code)

```typescript theme={null}
// Example: Generate secure OTP
import crypto from 'crypto';

function generateOTP(): string {
  return crypto.randomInt(100000, 999999).toString();
}
```

## Common Issues

| Issue               | Solution                                                                     |
| ------------------- | ---------------------------------------------------------------------------- |
| Template rejected   | Ensure category is AUTHENTICATION and content is purely verification-related |
| Code not displaying | Check that variable `{{1}}` is provided in `templateVariables`               |
| One-Tap not working | Verify `packageName` and `signatureHash` are correct for your Android app    |
| Slow delivery       | Check recipient's WhatsApp status and your sender's quality rating           |
| Button not showing  | Ensure the template has `buttons` configured with OTP type                   |

## Webhook Events

Track OTP delivery status via webhooks:

```json theme={null}
{
  "id": "evt_1702000000000_abc123",
  "type": "message.delivered",
  "timestamp": 1702000000000,
  "data": {
    "messageId": "msg_xyz789",
    "to": "+14155551234",
    "channel": "whatsapp",
    "status": "delivered"
  }
}
```

Configure webhooks to receive:

* `message.sent` - OTP accepted by WhatsApp
* `message.delivered` - OTP delivered to device
* `message.failed` - Delivery failed

See [Webhooks Guide](/guides/receiving-messages/webhooks) for setup instructions.

## Next Steps

<CardGroup cols={2}>
  <Card title="Template Approval" icon="check-circle" href="/guides/whatsapp/templates/approval">
    Learn more about the approval process and common rejection reasons
  </Card>

  <Card title="Configure Webhooks" icon="webhook" href="/guides/receiving-messages/webhooks">
    Set up webhooks to track delivery status
  </Card>

  <Card title="Smart Routing" icon="route" href="/guides/sending-messages/smart-routing">
    Use smart routing to automatically fall back to SMS if WhatsApp fails
  </Card>

  <Card title="Rate Limiting" icon="gauge" href="/concepts/rate-limiting">
    Understand API rate limits for high-volume OTP sending
  </Card>
</CardGroup>
