Guides
Sandbox & testing
The sandbox is a complete copy of the API that spends play money and simulates results, so you can build and test everything without launching a real campaign.
What is different
Sandbox (bv_test_) | Production (bv_live_) | |
|---|---|---|
| Wallet | 5,000,000 NGN of play money | Your real balance |
| Campaign ids | cmp_test_... | cmp_... |
| Approval | You trigger it with simulate | ByteVerse reviews it |
| Results | You trigger them with simulate | Real people, verified by ByteVerse |
| Webhooks | Separate test endpoints, real signed deliveries | Separate live endpoints |
| Same rules | Validation, pricing, refunds, idempotency, rate limits | Identical |
Simulating a campaign
Use POST /v1/sandbox/campaigns/{id}/simulate with one of three events:
| event | Effect | Fires |
|---|---|---|
activate | in_review to active | campaign.activated |
progress | Adds actions verified actions (about 10% extra are recorded as rejected) | campaign.progress at each 25/50/75/100% crossed |
complete | Verifies all remaining actions | Progress milestones + campaign.completed |
curl -X POST "https://api.byteverseafrica.com/v1/sandbox/campaigns/cmp_test_4b7e91d0a2c85f13/simulate" \
-H "Authorization: Bearer $BYTEVERSE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"event": "complete"
}'const res = await fetch("https://api.byteverseafrica.com/v1/sandbox/campaigns/cmp_test_4b7e91d0a2c85f13/simulate", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.BYTEVERSE_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"event": "complete"
}),
});
const data = await res.json();import os, requests
res = requests.post(
"https://api.byteverseafrica.com/v1/sandbox/campaigns/cmp_test_4b7e91d0a2c85f13/simulate",
headers={"Authorization": f"Bearer {os.environ['BYTEVERSE_API_KEY']}"},
json={
"event": "complete",
},
timeout=30,
)
data = res.json()<?php
$ch = curl_init("https://api.byteverseafrica.com/v1/sandbox/campaigns/cmp_test_4b7e91d0a2c85f13/simulate");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_HTTPHEADER => ["Authorization: Bearer " . getenv("BYTEVERSE_API_KEY"), "Content-Type: application/json"],
CURLOPT_POSTFIELDS => json_encode([
"event" => "complete",
]),
]);
$data = json_decode(curl_exec($ch), true);Sandbox submissions
Test campaigns have submissions too, so you can build your review screen without a live campaign. As you simulate progress, GET /v1/campaigns/{id}/submissions returns matching sample submissions (approved, rejected and pending) with ids that start sub_test_. They contain no personal data because none exists. In the sandbox the evidence endpoint returns an empty urls list, and submission.* webhooks are not sent.
A good test plan
- Create a campaign and confirm the wallet dropped by
total_charged. - Create one with a budget below
min_budgetand confirm you handlebudget_too_low. - Retry a creation with the same
Idempotency-Keyand confirm only one campaign exists. - Drain the wallet and confirm you handle
402 insufficient_funds. - Simulate
activate,progressandcompleteand confirm each webhook arrives and verifies. - Return a 500 from your webhook endpoint once and watch the retry in the deliveries list.
- Cancel campaigns in
in_reviewandactiveand check the refund amounts. - Simulate progress, then list submissions and page through them with
starting_after.
The repository ships a tests/smoke.sh script that runs the core flow against a test key.
