Testing & Sandbox
Develop and test without affecting production or using real WhatsApp/OTP credits.
Simulation Mode
Set WHATSAPP_SIMULATE_API=true in your .env file to enable simulation mode.
In simulation mode:
- WhatsApp API calls are mocked — requests succeed but no actual messages are sent to WhatsApp or the customer
- OTP API calls are mocked — OTPs are generated and tracked, but no SMS is sent via WhatsApp
- Responses are realistic — You get the same response format as production, enabling testing without side effects
Enabling Simulation Locally
# .env (local development)
WHATSAPP_SIMULATE_API=true
Then:
# Test WhatsApp send
curl -X POST http://localhost:8003/api/whatsapp/send-text \
-H "Authorization: Bearer token" \
-H "Content-Type: application/json" \
-d '{
"business_account_id": 1,
"to": "+201001234567",
"message": "Test message"
}'
# Returns simulated success (no actual send to WhatsApp)
{
"success": true,
"message_id": 1,
"whatsapp_message_id": "sim_abc123"
}
Testing OTP Locally
Send Test OTP
curl -X POST http://localhost:8003/api/v1/otp/send \
-H "X-Wallin-OTP-Key: test_key" \
-H "Content-Type: application/json" \
-d '{"phone_number": "+201001234567"}'
With simulation mode, the OTP is generated but not actually sent. You can see generated codes in logs or the database.
Verify Test OTP
To verify, you need to know the generated code. In simulation/test mode:
# Extract the code from the database or logs
SELECT code_hash, attempts FROM otp_requests WHERE reference_id = '...';
# Then verify (codes are usually "000000" in test mode or check logs)
curl -X POST http://localhost:8003/api/v1/otp/verify \
-H "X-Wallin-OTP-Key: test_key" \
-H "Content-Type: application/json" \
-d '{"reference_id": "550e8400...", "code": "000000"}'
Webhook Testing (Local)
Verify Webhook Signature
Wallin signs incoming webhooks with X-Wallin-Signature (HMAC-SHA256 of the request body using your connection's secret key).
To test locally:
-
Get your connection's webhook secret at
/channels/{connectionId}→ Webhooks -
Sign a test payload:
PAYLOAD='{"object":"whatsapp_business_account","entry":[...]}' SECRET='your_webhook_secret' SIGNATURE=$(echo -n "$PAYLOAD" | openssl dgst -sha256 -mac HMAC -macopt key="$SECRET" | cut -d' ' -f2) -
Send to your local webhook endpoint:
curl -X POST http://localhost:8003/api/whatsapp/webhook \ -H "X-Wallin-Signature: $SIGNATURE" \ -H "Content-Type: application/json" \ -d "$PAYLOAD"
Using ngrok for Testing Webhooks
To receive actual Meta webhooks on your local machine:
- Install ngrok:
brew install ngrok(or download) - Start ngrok:
ngrok http 8003 - Copy the forwarding URL (e.g.,
https://abc123.ngrok.io) - In your Meta App Dashboard, set Webhook URL to:
https://abc123.ngrok.io/api/whatsapp/webhook - Verify token is configured in Wallin settings
- Test webhook from Meta dashboard → Test Webhook
ngrok forwards HTTPS traffic to your local HTTP server.
Local Development Setup
Prerequisites
# Docker-based (Sail)
./vendor/bin/sail up -d
# Database
./vendor/bin/sail artisan migrate
./vendor/bin/sail artisan db:seed (optional)
# Create a test user/agent
./vendor/bin/sail artisan tinker
# Then in Tinker:
# User::factory()->create(['email' => 'agent@test.com', 'password' => bcrypt('password')]);
Testing Endpoints
# 1. Login (get token)
TOKEN=$(curl -s -X POST http://localhost:8003/api/agent/login \
-H "Content-Type: application/json" \
-d '{"email":"agent@test.com","password":"password","device_name":"test"}' \
| jq -r '.token')
echo "Token: $TOKEN"
# 2. Test Agent API
curl -X GET http://localhost:8003/api/agent/conversations \
-H "Authorization: Bearer $TOKEN"
# 3. Test WhatsApp API (simulation)
curl -X POST http://localhost:8003/api/whatsapp/send-text \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"business_account_id":1,"to":"+201001234567","message":"Test"}'
Database Reset
After testing, reset your database to a clean state:
# Reset and re-seed
./vendor/bin/sail artisan migrate:refresh --seed
# Or drop all tables and re-migrate
./vendor/bin/sail artisan migrate:fresh
Log Inspection
View API logs during testing:
# Watch logs in real-time
./vendor/bin/sail tail -f
# Or view specific file
./vendor/bin/sail tail storage/logs/laravel.log
Look for:
- Request/response pairs
- Webhook processing
- API errors
Testing Best Practices
- Use simulation mode for WhatsApp/OTP development
- Test in isolation — verify each API separately
- Use fixtures/factories for consistent test data
- Log everything — save request/response for debugging
- Test error cases — invalid input, rate limits, auth failures
- Use Postman/Insomnia for manual API exploration
Example Postman flow:
1. POST /api/agent/login → save {{token}}
2. GET /api/agent/conversations → list conversations
3. GET /api/agent/conversations/1 → get one conversation
4. POST /api/agent/conversations/1/messages/text → send message
5. GET /api/agent/conversations/1/messages → view messages
CI/CD Testing
For automated tests, use simulation mode in your CI pipeline:
# .github/workflows/test.yml
env:
WHATSAPP_SIMULATE_API: true
DB_DATABASE: testing
script:
- ./vendor/bin/sail artisan test
This runs tests against a simulated WhatsApp API, avoiding external dependencies and reducing test time.
Debugging API Calls
Using curl -v
curl -v -X POST http://localhost:8003/api/agent/login \
-H "Content-Type: application/json" \
-d '{"email":"agent@test.com","password":"password","device_name":"test"}'
# Shows:
# > request headers
# < response headers
# < response body
Using jq for JSON Parsing
# Pretty-print JSON
curl -s http://... | jq .
# Extract specific field
curl -s http://... | jq '.token'
# Conditional filter
curl -s http://... | jq '.data[] | select(.status == "active")'
Timing Requests
time curl -X GET http://localhost:8003/api/agent/conversations \
-H "Authorization: Bearer $TOKEN"
# Shows request duration