JavaScript / TypeScript SDK
Official Node.js SDK for Snippe — payments, payouts, and webhook verification
The official Snippe SDK for Node.js, written in TypeScript. Works in Node.js 18+ using the built-in fetch.
- Source: github.com/Neurotech-HQ/snippe-js-sdk
- npm:
@snippe/sdk
Install
npm install @snippe/sdkInitialize the client
import { Snippe } from "@snippe/sdk";
const snippe = new Snippe({
apiKey: process.env.SNIPPE_API_KEY!, // snp_...
webhookUrl: "https://example.com/webhooks/snippe",
});The webhookUrl you pass to the constructor is used as the default for every request — you can still override it per call.
Never embed your API key in client-side code. Keep it in a server-only environment variable.
Create a payment
const payment = await snippe.payments.create({
payment_type: "mobile",
details: { amount: 500 },
phone_number: "255781000000",
customer: {
firstname: "Jane",
lastname: "Doe",
email: "jane@example.com",
},
metadata: { order_id: "ORD-12345" },
});The customer's phone receives a USSD push to authorize. See Mobile Money payments for the full request shape.
const payment = await snippe.payments.create({
payment_type: "card",
details: {
amount: 1000,
redirect_url: "https://yoursite.com/success",
cancel_url: "https://yoursite.com/cancel",
},
phone_number: "255781000000",
customer: {
firstname: "Jane",
lastname: "Doe",
email: "jane@example.com",
address: "123 Main St",
city: "Dar es Salaam",
state: "DSM",
postcode: "14101",
country: "TZ",
},
});
// Redirect the customer
res.redirect(payment.payment_url);const payment = await snippe.payments.create({
payment_type: "dynamic-qr",
details: { amount: 500 },
phone_number: "255781000000",
customer: {
firstname: "Jane",
lastname: "Doe",
email: "jane@example.com",
},
});
// Render payment.payment_qr_code as a QR imageSend a payout
const payout = await snippe.payouts.send({
amount: 50000,
channel: "mobile",
recipient_phone: "255781000000",
recipient_name: "Employee Name",
narration: "Salary January 2026",
});Snippe auto-detects the provider (M-Pesa, Airtel, Mixx, Halotel) from the phone number.
await snippe.payouts.send({
amount: 50000,
channel: "bank",
recipient_bank: "CRDB",
recipient_account: "0150000000000",
recipient_name: "Vendor Ltd",
});See the full bank code list.
Calculate fees first
const { fee_amount, total_amount } = await snippe.payouts.fee({ amount: 50000 });Verify a webhook
The SDK ships a verifyWebhook helper that performs HMAC-SHA256 verification, replay-attack rejection, and JSON parsing in one call. Pass the raw request body as a Buffer — parsing and re-serializing the JSON will break the signature.
import express from "express";
import { verifyWebhook, SnippeWebhookVerificationError } from "@snippe/sdk";
const app = express();
app.post(
"/webhooks/snippe",
express.raw({ type: "application/json" }),
(req, res) => {
try {
const event = verifyWebhook({
rawBody: req.body,
signature: req.header("X-Webhook-Signature"),
timestamp: req.header("X-Webhook-Timestamp"),
signingKey: process.env.SNIPPE_WEBHOOK_SECRET!,
});
switch (event.type) {
case "payment.completed":
console.log("paid", event.data.reference, event.data.amount.value);
break;
}
res.status(200).send("OK");
} catch (err) {
if (err instanceof SnippeWebhookVerificationError) {
res.status(400).send("Invalid signature");
} else {
res.status(500).send("Internal error");
}
}
},
);For the full list of event types and payload shape, see the Webhooks guide.
Errors
The SDK throws typed errors you can branch on:
| Class | When it's thrown |
|---|---|
SnippeValidationError | 400 from the API — fix the request and retry |
SnippeRateLimitError | 429 — back off using X-Ratelimit-Reset |
SnippeWebhookVerificationError | Webhook signature or timestamp invalid |
SnippeError | Base class for everything else |
import { SnippeRateLimitError } from "@snippe/sdk";
try {
await snippe.payments.create({ /* ... */ });
} catch (err) {
if (err instanceof SnippeRateLimitError) {
// back off and retry
}
throw err;
}See Error handling for the full error code list.
Idempotency
Pass an idempotencyKey (max 30 characters) on any mutating call to safely retry without creating duplicates:
await snippe.payments.create(
{ /* ... */ },
{ idempotencyKey: "order-12345-attempt-1" },
);