SDKs

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.


Install

npm install @snippe/sdk

Initialize 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 image

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

ClassWhen it's thrown
SnippeValidationError400 from the API — fix the request and retry
SnippeRateLimitError429 — back off using X-Ratelimit-Reset
SnippeWebhookVerificationErrorWebhook signature or timestamp invalid
SnippeErrorBase 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" },
);

On this page