Skip to main content

Overview

SMS verification enables contract parties to confirm their agreements and provide digital signatures using simple text messages. This approach works on any mobile phone, requiring no smartphone or internet access.

Universal Access

Works on any phone - no smartphone or data required

Simple Commands

Reply with YES/NO commands to confirm or reject contracts

Auto-Generated

System sends contract summaries automatically after voice processing

Secure Codes

Cryptographic verification codes ensure authenticity

How It Works

1

Contract Created

After voice processing, the system generates a contract and assigns a unique ID.
2

SMS Sent

Each party receives an SMS with contract summary and confirmation instructions.
3

Party Responds

Users reply with “YES-” to confirm or “NO-” to decline.
4

Signature Recorded

The system records the confirmation as a digital signature with timestamp.

SMS Message Format

Contract confirmation messages follow a standard format:
VoicePact Contract:
ID: AG-2024-001234
Product: Maize (100 bags)
Value: KES 350,000

Reply YES-AG-2024-001234 to confirm
Reply NO-AG-2024-001234 to decline

Message Components

  • Contract ID: Unique identifier for tracking
  • Product Description: What’s being exchanged
  • Value: Total contract amount
  • Reply Instructions: Clear confirmation commands

Sending Contract SMS

Send contract notification to parties:
import httpx

response = httpx.post(
    "https://api.voicepact.com/api/v1/sms/send/contract",
    json={
        "contract_id": "AG-2024-001234",
        "recipients": [
            "+254712345678",
            "+254723456789"
        ],
        "terms": {
            "product": "Maize",
            "quantity": 100,
            "unit": "bags",
            "total_amount": 350000,
            "currency": "KES"
        }
    }
)

result = response.json()
print(f"SMS sent to {len(result['recipients'])} recipients")
print(f"Message: {result['message']}")

Response Format

{
  "contract_id": "AG-2024-001234",
  "recipients": ["+254712345678", "+254723456789"],
  "message": "VoicePact Contract:\nID: AG-2024-001234\nProduct: Maize (100 bags)\nValue: KES 350,000\n\nReply YES-AG-2024-001234 to confirm\nReply NO-AG-2024-001234 to decline",
  "sms_result": {
    "status": "success",
    "data": {
      "SMSMessageData": {
        "Recipients": [
          {
            "statusCode": 101,
            "number": "+254712345678",
            "status": "Success",
            "messageId": "ATXid_abc123"
          }
        ]
      }
    }
  }
}

Sending Simple SMS

For general notifications or custom messages:
response = httpx.post(
    "https://api.voicepact.com/api/v1/sms/send",
    json={
        "phoneNumber": "+254712345678",
        "message": "Your contract has been confirmed by all parties."
    }
)

result = response.json()
if result["status"] == "success":
    print("Message sent successfully")

Bulk SMS

Send the same message to multiple recipients:
response = httpx.post(
    "https://api.voicepact.com/api/v1/sms/send/bulk",
    json={
        "recipients": [
            "+254712345678",
            "+254723456789",
            "+254734567890"
        ],
        "message": "Contract delivery scheduled for tomorrow."
    }
)

result = response.json()
print(f"Status: {result['status']}")

Webhook Handler

VoicePact automatically processes incoming SMS replies:
# Incoming webhook at /api/v1/sms/webhook
{
  "from": "+254712345678",
  "to": "40404",
  "text": "YES-AG-2024-001234",
  "date": "2024-03-06 10:30:00",
  "id": "ATXid_xyz789"
}

Processing Logic

The webhook handler at sms.py:294:
  1. Receives incoming SMS
  2. Parses the message text
  3. Extracts action (YES/NO) and contract ID
  4. Records the signature
  5. Updates contract status
  6. Notifies other parties
# Example webhook processing
if message.startswith("YES-"):
    contract_id = message.split("-", 1)[1]
    # Record confirmation signature
    await record_signature(
        contract_id=contract_id,
        phone_number=sender,
        method="sms_confirmation",
        status="signed"
    )
elif message.startswith("NO-"):
    contract_id = message.split("-", 1)[1]
    # Record rejection
    await record_signature(
        contract_id=contract_id,
        phone_number=sender,
        method="sms_confirmation",
        status="rejected"
    )

SMS Service Status

Check SMS service availability:
response = httpx.get("https://api.voicepact.com/api/v1/sms/status")
status = response.json()

print(f"Service: {status['service']}")
print(f"Available: {status['service_available']}")
print(f"Message: {status['message']}")

Response Format

{
  "service": "SMS",
  "username": "voicepact",
  "api_key_set": true,
  "service_available": true,
  "message": "SMS service ready",
  "timestamp": "2024-03-06T10:30:00Z"
}

Message Templates

VoicePact provides built-in message templates:

Contract Summary

from app.services.africastalking_client import get_fixed_at_client

client = get_fixed_at_client()
message = client.generate_contract_sms(
    contract_id="AG-2024-001234",
    terms={
        "product": "Maize",
        "quantity": 100,
        "unit": "bags",
        "total_amount": 350000,
        "currency": "KES",
        "delivery_deadline": "2024-03-15"
    }
)
print(message)
Output:
VoicePact Contract Summary:
ID: AG-2024-001234
Product: Maize (100 bags)
Total: KES 350,000.00, Due: 2024-03-15
Reply YES-AG-2024-001234 to confirm or NO-AG-2024-001234 to decline
See africastalking_client.py:408 for template implementation.

Phone Number Formatting

The system automatically formats phone numbers:
client = await get_africastalking_client()

# All these formats are accepted:
formatted = await client.format_phone_number("0712345678")  # Local
formatted = await client.format_phone_number("712345678")   # Without prefix  
formatted = await client.format_phone_number("+254712345678") # International

# All return: "+254712345678"
See africastalking_client.py:397 for formatting logic.

Delivery Reports

Track SMS delivery status through the webhook:
# Delivery report webhook payload
{
  "id": "ATXid_abc123",
  "status": "Success",
  "phoneNumber": "+254712345678",
  "retryCount": 0,
  "failureReason": null
}

Status Values

  • Success: Message delivered
  • Sent: Message sent to carrier
  • Buffered: Queued for delivery
  • Rejected: Invalid number or blocked
  • Failed: Delivery failed

Error Handling

try:
    response = httpx.post("/api/v1/sms/send", json=payload)
    response.raise_for_status()
    result = response.json()
    
    if result["status"] == "error":
        print(f"SMS failed: {result['message']}")
        
except httpx.HTTPStatusError as e:
    if e.response.status_code == 503:
        print("SMS service unavailable - check configuration")
    elif e.response.status_code == 500:
        print(f"Send failed: {e.response.json()['detail']}")

Cost Optimization

Keep messages under 160 characters to avoid multiple SMS charges:
  • Use short contract IDs
  • Abbreviate when possible
  • Remove unnecessary words
Use bulk SMS endpoint for multiple recipients:
  • Single API call
  • Better throughput
  • Reduced overhead
Enable delivery reports only when needed:
  • Track critical confirmations
  • Monitor failed deliveries
  • Retry failed messages

Security Considerations

SMS Confirmation Codes

The system generates cryptographic verification codes:
from app.services.crypto_service import get_crypto_service

crypto = get_crypto_service()
code = crypto.generate_sms_confirmation_code(
    contract_id="AG-2024-001234",
    phone_number="+254712345678"
)
print(f"Verification code: {code}")  # 6-digit numeric code
See crypto_service.py:101 for code generation.

Signature Recording

Each SMS confirmation creates a digital signature record:
  • Timestamp: When the confirmation was received
  • Phone Number: Who confirmed
  • IP Address: Source of webhook (if available)
  • Signature Hash: Cryptographic proof
  • Signature Data: Original message content
See the ContractSignature model at contract.py:173.

Best Practices

  1. Clear Instructions: Always include reply format in messages
  2. Unique IDs: Use contract IDs in confirmation commands
  3. Confirmation Messages: Send acknowledgment after receiving confirmation
  4. Retry Logic: Implement exponential backoff for failed sends
  5. Rate Limiting: Respect carrier rate limits
  6. Local Numbers: Use local sender IDs when available

Testing

Test SMS integration without sending real messages:
response = httpx.post(
    "https://api.voicepact.com/api/v1/sms/test",
    params={"phone_number": "+254712345678"}
)

result = response.json()
print(f"Test status: {result['status']}")
print(f"Message: {result.get('message')}")
See test endpoint at sms.py:142.

Integration with Africa’s Talking

VoicePact uses Africa’s Talking SMS API:

Next Steps

Digital Signatures

Understand cryptographic signatures

USSD Integration

Alternative confirmation via USSD

Voice Contracts

Create contracts from voice calls

Mobile Money

Process payments

Build docs developers (and LLMs) love