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

http
https://grebo.tesloty.com/api/v1

This 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 0712345678 or 255712345678 — we normalize.
  • All timestamps are ISO 8601 in UTC.
  • Every successful response includes an id you 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

  1. Create an account and verify your phone number.
  2. Submit a Business Application if you want live production traffic.
  3. Open API Keys and generate a key. Copy it immediately.
  4. Run the balance call below first. If that works, the rest of the integration is just the same pattern.
Fastest path: use /api/v1/balance to confirm your key is valid, then use /api/v1/deposits or /api/v1/withdrawals for real flows.

cURL

curl
curl https://grebo.tesloty.com/api/v1/balance \
  -H "Authorization: Bearer tsl_live_xxxxxxxxxxxx"

Node.js (fetch)

javascript
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
<?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

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.

Important: the webhook signing secret (often starting withwhsec_) is only for verifyingx-webhook-signature andx-webhook-timestamp. It is not the API key you send in Authorization: Bearer ....
http
Authorization: Bearer tsl_live_xxxxxxxxxxxx
Content-Type: application/json

Compromised 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

GET/api/v1/balance

Get your live TZS wallet balance and remaining SMS credits.

json
{
  "status": "success",
  "data": {
    "balance": 1250,
    "currency": "TZS"
  }
}
GET/api/v1/transactions?limit=50

Recent 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.

json
{
  "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.

POST/api/v1/deposits
json
{
  "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.

json
{
  "status": "success",
  "data": {
    "id": "tx_01HZABCDEF...",
    "status": "pending",
    "amount": 1000,
    "method": "mobile",
    "payment_url": null,
    "reference": "ORDER-2026-001"
  }
}

Node.js example

javascript
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.

POST/api/v1/withdrawals

Mobile wallet

json
{
  "amount": 5000,
  "method": "mobile",
  "recipient": "255712345678",
  "reference": "PAYOUT-001"
}

Bank account

json
{
  "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

GET/api/v1/withdrawals/preview?method=mobile&amount=50000
json
{
  "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.

GET/api/v1/sms/balance
json
{
  "status": "success",
  "data": {
    "sms_credits": 1500,
    "rate_tzs": 28
  }
}
GET/api/v1/sms/sender-ids
json
[
  { "sender_id": "MYSHOP", "status": "approved" },
  { "sender_id": "PROMO",  "status": "pending" }
]
POST/api/v1/sms/send
json
{
  "recipient": "255712345678",
  "message": "Karibu MYSHOP! Order #1234 imepokelewa.",
  "sender_id": "MYSHOP"
}
POST/api/v1/sms/send-bulk
json
{
  "sender_id": "MYSHOP",
  "message": "Promo siku 3 tu — 20% punguzo!",
  "recipients": ["255712345678", "255756111222", "255744333444"]
}

PHP example

php
<?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

json
{
  "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.

javascript
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/send and 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:

http
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
Retry-After: 12

Need 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:

json
{
  "error": "insufficient_balance",
  "message": "Wallet does not have enough funds.",
  "request_id": "req_01HZABC..."
}
  • 400 invalid_request — malformed JSON or missing fields
  • 401 unauthorized — missing, expired or revoked API key
  • 402 insufficient_balance — wallet or SMS credits too low
  • 403 not_business_verified — account is not approved for live API use
  • 404 not_found — unknown id (transaction, sender ID, etc.)
  • 409 duplicate_reference — your reference was already used (idempotency protection)
  • 422 validation_failed — field-level validation error (see details)
  • 429 rate_limited — slow down; see Retry-After header
  • 500/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.

Open API Keys