Grebo API · v1
Build with Grebo
Start with one API key, one balance call, and one webhook URL. This page is written to help you integrate in minutes without guessing the response format.
Overview
Introduction
Grebo (by Tesloty) gives you a single integration to collect money (Mobile Money, Cards, QR), send money (mobile wallets & Tanzanian bank accounts), and send Bulk SMS from your approved Sender ID — all behind one REST API and one key.
Base URL
https://grebo.tesloty.com/api/v1This is the deployed production API host for the Railway app. Usehttps://grebo.tesloty.com/api/v1 in all examples below.
Conventions
- All requests and responses are
application/json; charset=utf-8. - Amounts are passed in whole TZS (not cents). Example:
1500= TSh 1,500. - Phone numbers may be sent as
0712345678or255712345678— we normalize. - All timestamps are ISO 8601 in UTC.
- Every successful response includes an
idyou can use for reconciliation.
Current Pricing (Grebo)
- Bulk SMS: TZS 28 / SMS (live price — may be tuned by admin and is returned by the API).
- Cash-Out (Withdrawals): 4% flat on all amounts.
- Deposits (mobile money / card): no fee on top of the provider rate.
5-minute setup
Quickstart
- Create an account and verify your phone number.
- Submit a Business Application if you want live production traffic.
- Open API Keys and generate a key. Copy it immediately.
- Run the balance call below first. If that works, the rest of the integration is just the same pattern.
/api/v1/balance to confirm your key is valid, then use /api/v1/deposits or /api/v1/withdrawals for real flows.cURL
curl https://grebo.tesloty.com/api/v1/balance \
-H "Authorization: Bearer tsl_live_xxxxxxxxxxxx"Node.js (fetch)
const BASE_URL = process.env.GREBO_API_BASE_URL ?? "https://grebo.tesloty.com";
const res = await fetch(`${BASE_URL}/api/v1/balance`, {
headers: { Authorization: `Bearer ${process.env.GREBO_API_KEY}` },
});
const body = await res.json();
console.log(body);
// body.status === "success"
// body.data.balance === 1250
// body.data.currency === "TZS"PHP
<?php
$baseUrl = getenv("GREBO_API_BASE_URL") ?: "https://grebo.tesloty.com";
$ch = curl_init($baseUrl . "/api/v1/balance");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Authorization: Bearer " . getenv("GREBO_API_KEY"),
]);
$response = curl_exec($ch);
$data = json_decode($response, true);
print_r($data);Python
import os, requests
BASE_URL = os.environ.get("GREBO_API_BASE_URL", "https://grebo.tesloty.com")
res = requests.get(
f"{BASE_URL}/api/v1/balance",
headers={"Authorization": f"Bearer {os.environ['GREBO_API_KEY']}"},
timeout=15,
)
print(res.json())Security
Authentication
All requests authenticate with a Bearer API key. Keys are shown once at creation — store them in your server secrets (env vars, Vault, AWS Secrets Manager). Never embed keys in mobile apps or browser code.
whsec_) is only for verifyingx-webhook-signature andx-webhook-timestamp. It is not the API key you send in Authorization: Bearer ....Authorization: Bearer tsl_live_xxxxxxxxxxxx
Content-Type: application/jsonCompromised a key? Revoke it from /dashboard/api-keys and generate a new one — subsequent requests with the old key return 401 within seconds.
What to use where
- API key: the value generated in your dashboard, sent as the Bearer token in every REST request.
- Webhook signing secret: the separate secret shown on the API Keys page, used only to verify webhook signatures in your own server.
Endpoints
Wallet
/api/v1/balanceGet your live TZS wallet balance and remaining SMS credits.
{
"status": "success",
"data": {
"balance": 1250,
"currency": "TZS"
}
}/api/v1/transactions?limit=50Recent transaction history for deposits, withdrawals and SMS top-ups. The current API returns the latest items directly, so you can render them immediately in your dashboard or admin panel.
{
"status": "success",
"data": [
{
"id": "tx_01HZ...",
"type": "deposit",
"method": "mobile",
"amount_cents": 500000,
"status": "completed",
"created_at": "2026-05-31T08:21:00Z",
"reference": "INV-001"
}
]
}Collect
Deposits
Initiate a Mobile Money (USSD push), Card or Dynamic QR collection. Grebo automatically routes the request through the best available rail.
/api/v1/deposits{
"amount": 1000,
"method": "mobile",
"phone": "255712345678",
"reference": "ORDER-2026-001",
"callback_url": "https://yourapp.com/webhooks/grebo"
}Response — for mobile a USSD push is triggered immediately; for card / qr you redirect the customer to payment_url.
{
"status": "success",
"data": {
"id": "tx_01HZABCDEF...",
"status": "pending",
"amount": 1000,
"method": "mobile",
"payment_url": null,
"reference": "ORDER-2026-001"
}
}Node.js example
const BASE_URL = process.env.GREBO_API_BASE_URL ?? "https://grebo.tesloty.com";
const res = await fetch(`${BASE_URL}/api/v1/deposits`, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.GREBO_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
amount: 1000,
method: "mobile",
phone: "255712345678",
reference: "ORDER-2026-001",
callback_url: "https://yourapp.com/webhooks/grebo",
}),
});
const tx = await res.json();Final status is delivered via webhook (transaction.completed or transaction.failed) — do not block on the synchronous response.
Disburse
Withdrawals
Send money to a mobile wallet or a Tanzanian bank account.
/api/v1/withdrawalsMobile wallet
{
"amount": 5000,
"method": "mobile",
"recipient": "255712345678",
"reference": "PAYOUT-001"
}Bank account
{
"amount": 50000,
"method": "bank",
"recipient": "0150123456789",
"bank_code": "CRDB",
"account_name": "John Doe",
"reference": "PAYOUT-002"
}Supported bank codes: CRDB, NMB, NBC, EQUITY, EXIM, STANBIC, DTB, AKIBA, AZANIA, BOA.
Fee preview
/api/v1/withdrawals/preview?method=mobile&amount=50000{
"status": "success",
"data": {
"amount_tzs": 50000,
"fee_tzs": 2500,
"total_debited_tzs": 52500
}
}Messaging
Bulk SMS
Send transactional or marketing SMS using your approved Sender ID when you have one, or fall back to the default GREBO sender. Credits are pre-paid from your wallet.
/api/v1/sms/balance{
"status": "success",
"data": {
"sms_credits": 1500,
"rate_tzs": 28
}
}/api/v1/sms/sender-ids[
{ "sender_id": "MYSHOP", "status": "approved" },
{ "sender_id": "PROMO", "status": "pending" }
]/api/v1/sms/send{
"recipient": "255712345678",
"message": "Karibu MYSHOP! Order #1234 imepokelewa.",
"sender_id": "MYSHOP"
}/api/v1/sms/send-bulk{
"sender_id": "MYSHOP",
"message": "Promo siku 3 tu — 20% punguzo!",
"recipients": ["255712345678", "255756111222", "255744333444"]
}PHP example
<?php
$baseUrl = getenv("GREBO_API_BASE_URL") ?: "https://grebo.tesloty.com";
$ch = curl_init($baseUrl . "/api/v1/sms/send");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Authorization: Bearer " . getenv("GREBO_API_KEY"),
"Content-Type: application/json",
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
"recipient" => "255712345678",
"message" => "Hello from PHP!",
"sender_id" => "MYSHOP",
]));
$res = json_decode(curl_exec($ch), true);⚠️ You need SMS credits before sending. If you have an approved Sender ID, it will be used; otherwise the default GREBO sender is used. Buy credits at /dashboard/sms.
Realtime
Webhooks
To receive final transaction updates, pass your own HTTPS callback_url in your API request. Grebo will POST JSON to that URL whenever a transaction reaches a final state. Each request is signed so you can verify it came from Grebo.
Use the webhook verification secret from your gateway/provider settings in your server-side listener only. Keep it private and never expose it in browser code or to end users.
This is your endpoint in your own app or system. You do not copy a Grebo callback URL back into your own webhook receiver.
Event payload
{
"event": "transaction.completed",
"data": {
"id": "tx_01HZABCDEF",
"type": "deposit",
"method": "mobile",
"amount_tzs": 1000,
"status": "completed",
"reference": "ORDER-2026-001",
"completed_at": "2026-05-31T08:23:11Z"
}
}Possible events: transaction.completed, transaction.failed, sms.delivered, sms.failed, sender_id.approved, sender_id.rejected.
Signature verification (Node.js)
The webhook receiver validates x-webhook-signature andx-webhook-timestamp. Build the HMAC over${timestamp}.${rawBody} using the webhook verification secret from your gateway/provider settings.
import crypto from "crypto";
export function verify(req, secret) {
const signature = req.headers["x-webhook-signature"];
const timestamp = req.headers["x-webhook-timestamp"];
const rawBody = req.rawBody.toString();
const message = `${timestamp}.${rawBody}`;
const expected = crypto.createHmac("sha256", secret).update(message).digest("hex");
const a = Buffer.from(expected, "utf8");
const b = Buffer.from(signature ?? "", "utf8");
return a.length === b.length && crypto.timingSafeEqual(a, b);
}Always respond with 200 OK within 5 seconds — we retry up to 8 times with exponential backoff (1m, 5m, 30m, 2h, 6h, 12h, 24h, 48h).
Throughput
Rate Limits
API requests are rate-limited per API key:
- 120 req/min on read endpoints (
/balance,/transactions). - 60 req/min on payment endpoints (
/deposits,/withdrawals). - 30 req/sec on
/sms/sendand 5 req/sec on/sms/send-bulk(max 1,000 recipients per call).
When you exceed a limit we return 429 Too Many Requests with these headers:
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
Retry-After: 12Need higher limits? Reach support inside the dashboard — we'll lift them for Business-verified accounts.
Reliability
Errors
Standard HTTP status codes. Error responses share this shape:
{
"error": "insufficient_balance",
"message": "Wallet does not have enough funds.",
"request_id": "req_01HZABC..."
}400invalid_request— malformed JSON or missing fields401unauthorized— missing, expired or revoked API key402insufficient_balance— wallet or SMS credits too low403not_business_verified— account is not approved for live API use404not_found— unknown id (transaction, sender ID, etc.)409duplicate_reference— yourreferencewas already used (idempotency protection)422validation_failed— field-level validation error (seedetails)429rate_limited— slow down; seeRetry-Afterheader500/502/503— transient server error (safe to retry with backoff)
Always include the request_id when contacting support — it lets us locate the exact request in our logs in seconds.
Ready to go live?
Generate a production API key and start integrating.
