Use the sandbox to create a plan, add a customer, collect a test payment method through hosted checkout, and watch the subscription engine mark the subscription active. Nothing here costs money or counts toward your bill.
Before you start
- Create a Subby account or sign in.
- Open Dashboard → Developers → API keys and make sure the environment switch is set to Sandbox.
- Copy your secret key. It starts with
sk_test_.
Keep secret keys on your server. Never put them in a mobile app, browser code or a public repository.
Set the key as an environment variable so the examples below work as written:
export SUBBY_SECRET_KEY="sk_test_xxxxxxxxxxxxxxxxxxxxxxxx"
export SUBBY_BASE_URL="https://sandbox-api.mysubbyapp.com/v1"
Step 1 — Check your key
curl "$SUBBY_BASE_URL/account" \
-H "Authorization: Bearer $SUBBY_SECRET_KEY"
You should see your account with "livemode": false. This is a GET request, so it is never metered.
Step 2 — Create a plan
A plan describes what you sell and how often you charge. Amounts are in kobo (₦1 = 100 kobo), so 500000 is ₦5,000.
curl "$SUBBY_BASE_URL/plans" \
-H "Authorization: Bearer $SUBBY_SECRET_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 6f1c2a1e-plan-pro-monthly" \
-d '{
"name": "Pro Monthly",
"amount": 500000,
"currency": "NGN",
"billing_cycle": "monthly",
"trial_days": 0,
"retry_logic": { "enabled": true, "max_attempts": 3, "retry_intervals_days": [1, 3, 7], "escalation_on_failure": "pause_service" },
"reminders": { "enabled": true, "channels": ["whatsapp", "sms", "email"], "pre_due_days": [3], "post_due_days": [1, 3] }
}'
const res = await fetch(`${process.env.SUBBY_BASE_URL}/plans`, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.SUBBY_SECRET_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': '6f1c2a1e-plan-pro-monthly',
},
body: JSON.stringify({
name: 'Pro Monthly',
amount: 500000,
currency: 'NGN',
billing_cycle: 'monthly',
retry_logic: { enabled: true, max_attempts: 3, retry_intervals_days: [1, 3, 7], escalation_on_failure: 'pause_service' },
reminders: { enabled: true, channels: ['whatsapp', 'sms', 'email'], pre_due_days: [3], post_due_days: [1, 3] },
}),
});
const { data: plan } = await res.json();
console.log(plan.id); // plan_...
import os, requests
res = requests.post(
f"{os.environ['SUBBY_BASE_URL']}/plans",
headers={
"Authorization": f"Bearer {os.environ['SUBBY_SECRET_KEY']}",
"Idempotency-Key": "6f1c2a1e-plan-pro-monthly",
},
json={
"name": "Pro Monthly",
"amount": 500000,
"currency": "NGN",
"billing_cycle": "monthly",
"retry_logic": {"enabled": True, "max_attempts": 3, "retry_intervals_days": [1, 3, 7], "escalation_on_failure": "pause_service"},
"reminders": {"enabled": True, "channels": ["whatsapp", "sms", "email"], "pre_due_days": [3], "post_due_days": [1, 3]},
},
)
plan = res.json()["data"]
print(plan["id"]) # plan_...
Save the returned id (it starts with plan_).
Step 3 — Create a customer
curl "$SUBBY_BASE_URL/customers" \
-H "Authorization: Bearer $SUBBY_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Ada Okafor",
"email": "ada@example.com",
"phone": "+2348000000001",
"metadata": { "your_user_id": "u_1024" }
}'
Save the customer id (it starts with cus_). Use metadata to store your own IDs so you can match records later.
+2348000000001is a sandbox test number. Nudges sent to it are marked delivered. See Testing in sandbox for numbers that simulate failures.
Step 4 — Start a checkout session
Checkout sessions give you a Subby-hosted page where the customer enters payment details. When they finish, the subscription is created.
curl "$SUBBY_BASE_URL/checkout-sessions" \
-H "Authorization: Bearer $SUBBY_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{
"mode": "subscription",
"customer": "cus_REPLACE_ME",
"plan": "plan_REPLACE_ME",
"success_url": "https://example.com/billing/success",
"cancel_url": "https://example.com/billing/cancelled"
}'
The response includes a url. Keep it for now: set up your webhook endpoint in Step 5 first, so you receive the events when checkout completes. Then open the url in your browser and choose Test payment method → Succeeds. Sessions expire after 24 hours.
Step 5 — Listen for webhooks
Create a webhook endpoint so your system hears about the new subscription. For local development, expose your server with a tunnelling tool, then register the URL:
curl "$SUBBY_BASE_URL/webhook-endpoints" \
-H "Authorization: Bearer $SUBBY_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-tunnel.example.com/webhooks/subby",
"enabled_events": ["subscription.created", "charge.succeeded", "charge.failed", "subscription.past_due", "subscription.paused"]
}'
The response contains a secret starting with whsec_. It is shown once, so store it now. You'll use it to verify signatures.
Step 6 — Confirm the subscription
curl "$SUBBY_BASE_URL/subscriptions?customer=cus_REPLACE_ME" \
-H "Authorization: Bearer $SUBBY_SECRET_KEY"
You should see one subscription with "status": "active".
What you just used
| Request | Method | Metered in live? |
|---|---|---|
| Check account | GET |
No |
| Create plan | POST |
Yes, 1 write request |
| Create customer | POST |
Yes, 1 write request |
| Create checkout session | POST |
Yes, 1 write request |
| Create webhook endpoint | POST |
Yes, 1 write request |
| List subscriptions | GET |
No |
In sandbox none of these count. In live, this flow uses 4 of your monthly write requests.
Next steps
- Simulate a failed payment and watch retries and nudges: Testing in sandbox.
- Learn every subscription state: Subscription lifecycle.
- When you're ready for real customers: Going live.