Skip to main content

Overview

The VoicePact API currently operates without mandatory authentication for most endpoints, making it accessible for underserved communities. However, the system includes built-in security configurations that can be enabled for production deployments.

Current Authentication Status

The current implementation does not enforce authentication on public endpoints. This is designed for ease of use in community deployments but should be enhanced for production use.

Security Configuration

VoicePact includes several security features configured through environment variables:

Secret Keys

# JWT token signing (prepared for future use)
SECRET_KEY=your-secret-key-here

# Token expiration (default: 7 days)
ACCESS_TOKEN_EXPIRE_MINUTES=10080

# Cryptographic signatures for contracts
SIGNATURE_PRIVATE_KEY=your-signature-key-here

# Password hashing salt
PASSWORD_SALT=your-salt-here

Webhook Security

Webhooks from external services (Africa’s Talking) can be secured:
# Webhook signature validation
WEBHOOK_SECRET=your-webhook-secret-here

# Base URL for webhook endpoints
WEBHOOK_BASE_URL=https://your-domain.com

Authentication Methods

1. No Authentication (Current Default)

Most endpoints are publicly accessible:
curl -X GET http://localhost:8000/api/v1/contracts/AG-2024-001

2. Phone Number Verification

Contract confirmations use phone number-based verification:
curl -X POST http://localhost:8000/api/v1/contracts/AG-2024-001/confirm \
  -H "Content-Type: application/json" \
  -d '{
    "phone_number": "+254712345678"
  }'
Only parties listed in the contract can confirm it using their registered phone numbers.

3. SMS-Based Authentication

Contracts are confirmed via SMS replies:
SMS: YES-AG-2024-001
From: +254712345678
The system validates:
  • Phone number is a contract party
  • SMS format is correct
  • Contract exists and is in pending state

Cryptographic Signatures

All contracts include cryptographic signatures for integrity:

Contract Hash

Each contract generates a unique hash using the Blake2b algorithm:
# From core/config.py
contract_hash_algorithm = "blake2b"  # or "sha256"
Example contract response:
{
  "contract_id": "AG-2024-001",
  "contract_hash": "a8f5f167f44f4964e6c998dee827110c",
  "status": "pending"
}

Digital Signatures

Signatures use the Ed25519 algorithm:
# From core/config.py
contract_signature_algorithm = "ed25519"  # or "rsa"

Implementing JWT Authentication (Future)

The codebase is prepared for JWT authentication. To implement:

1. Set Environment Variables

# Strong secret key for JWT signing
SECRET_KEY=$(openssl rand -hex 32)

# Token expiration in minutes
ACCESS_TOKEN_EXPIRE_MINUTES=60

2. Create Access Tokens

from datetime import datetime, timedelta
from jose import jwt
from app.core.config import get_settings

settings = get_settings()

def create_access_token(data: dict):
    to_encode = data.copy()
    expire = datetime.utcnow() + timedelta(
        minutes=settings.access_token_expire_minutes
    )
    to_encode.update({"exp": expire})
    
    return jwt.encode(
        to_encode,
        settings.get_secret_value("secret_key"),
        algorithm="HS256"
    )

3. Protect Endpoints

from fastapi import Depends, HTTPException
from fastapi.security import HTTPBearer

security = HTTPBearer()

async def verify_token(credentials = Depends(security)):
    token = credentials.credentials
    # Verify JWT token
    return decoded_token

@router.get("/protected")
async def protected_endpoint(token = Depends(verify_token)):
    return {"message": "Authenticated"}
For third-party integrations, implement API key authentication:

Generate API Key

python -c "import secrets; print(secrets.token_urlsafe(32))"

Usage Example

curl -X POST http://localhost:8000/api/v1/contracts/create \
  -H "X-API-Key: your-api-key-here" \
  -H "Content-Type: application/json" \
  -d '{...}'

Implement API Key Validation

from fastapi import Header, HTTPException

async def verify_api_key(x_api_key: str = Header(...)):
    if x_api_key != settings.api_key:
        raise HTTPException(
            status_code=403,
            detail="Invalid API key"
        )
    return x_api_key

@router.post("/secure-endpoint")
async def secure_endpoint(api_key = Depends(verify_api_key)):
    return {"message": "Authorized"}

Webhook Signature Verification

Verify webhooks from Africa’s Talking:
import hmac
import hashlib

def verify_webhook_signature(
    payload: str,
    signature: str,
    secret: str
) -> bool:
    expected = hmac.new(
        secret.encode(),
        payload.encode(),
        hashlib.sha256
    ).hexdigest()
    
    return hmac.compare_digest(expected, signature)
Usage in webhook endpoint:
@router.post("/webhook")
async def payment_webhook(
    request: Request,
    x_signature: str = Header(None)
):
    body = await request.body()
    
    if not verify_webhook_signature(
        body.decode(),
        x_signature,
        settings.get_secret_value("webhook_secret")
    ):
        raise HTTPException(403, "Invalid signature")
    
    # Process webhook

CORS Configuration

Control which domains can access the API:
# Single origin
CORS_ORIGINS=https://app.voicepact.com

# Multiple origins (comma-separated)
CORS_ORIGINS=https://app.voicepact.com,https://admin.voicepact.com

# Allow credentials
CORS_CREDENTIALS=true

# Allowed methods
CORS_METHODS=GET,POST,PUT,DELETE,OPTIONS

# Allowed headers
CORS_HEADERS=*

Security Best Practices

Always use HTTPS to encrypt data in transit. Configure your reverse proxy (nginx, Caddy) with SSL/TLS certificates.
server {
    listen 443 ssl http2;
    server_name api.voicepact.com;
    
    ssl_certificate /etc/ssl/cert.pem;
    ssl_certificate_key /etc/ssl/key.pem;
    
    location / {
        proxy_pass http://localhost:8000;
    }
}
Regularly rotate your secret keys and update environment variables:
# Generate new keys
NEW_SECRET=$(openssl rand -hex 32)
NEW_SIGNATURE=$(openssl rand -hex 64)

# Update environment
export SECRET_KEY=$NEW_SECRET
export SIGNATURE_PRIVATE_KEY=$NEW_SIGNATURE

# Restart application
systemctl restart voicepact
Implement rate limiting to prevent abuse:
from slowapi import Limiter
from slowapi.util import get_remote_address

limiter = Limiter(key_func=get_remote_address)

@app.post("/api/v1/contracts/create")
@limiter.limit("10/minute")
async def create_contract(request: Request):
    # Create contract
Always validate input data using Pydantic models:
from pydantic import BaseModel, Field, validator

class ContractCreate(BaseModel):
    total_amount: float = Field(..., gt=0, le=1000000)
    phone_number: str = Field(..., regex=r'^\+254\d{9}$')
    
    @validator('total_amount')
    def validate_amount(cls, v):
        if v < 100:  # Minimum amount
            raise ValueError('Amount too low')
        return v

Environment-Specific Settings

Different security levels for different environments:
ENVIRONMENT=development
DEBUG=true
SECRET_KEY=dev-secret-key
CORS_ORIGINS=http://localhost:3000

Next Steps

Error Handling

Learn how to handle API errors and exceptions

Contract Endpoints

Explore contract management endpoints

Build docs developers (and LLMs) love