Skip to content

221 SDKs

The official TypeScript, Python and Dart SDKs: installation, first call, creating a payment and verifying a webhook.

The SDKs are generated from the API's OpenAPI document. They verify webhook signatures and embed a snapshot of the small reference datasets (public holidays, operator prefixes, banks, geography down to the commune) for offline use.

Not published yet

The SDKs are not published yet: they will be available on npm, PyPI and pub.dev at release. The install commands below will work then. Until then, the API is called directly: see First call.

PackageLanguageVersion
@221/sdkTypeScript0.2.0
sdk-221 (module sdk221)Python0.2.0
sdk221Dart and Flutter0.2.0

The examples read API_URL (the base URL, https://apps.orvlabs.com), API_KEY (the key, shaped sk_221_pay_test_… or sk_221_pay_live_…) and WEBHOOK_SECRET from the environment. Payment routes require a key; data routes also work without one, with a low quota.

Idempotency-Key is unique to each payment: sending the same value again does not create a duplicate. A webhook is verified on the raw body, before any JSON decoding, with the X-221-Timestamp and X-221-Signature headers; after 5 minutes the signature is rejected. A delivery can be replayed: deduplicate on the event identifier. Field details are in 221 Pay.

TypeScript

Installation
npm install @221/sdk
payment.ts
import { createApiClient } from '@221/sdk';

const api = createApiClient({ baseUrl: process.env.API_URL, apiKey: process.env.API_KEY });
const { data, error } = await api.POST('/v1/payments', {
  params: { header: { 'Idempotency-Key': 'order-1042' } },
  body: {
    amount: '5000',
    currency: 'XOF',
    rail: 'sn_wave',
    merchant_reference: 'order-1042',
    return_url: 'https://shop.example/payment/return',
    customer_phone: '77 123 45 67',
  },
});
if (error) throw new Error(`${error.code}: ${error.message}`);
webhook.ts
import { verifyWebhook } from '@221/sdk/webhook';

// Express: app.post('/webhooks/221', express.raw({ type: 'application/json' }), (req, res) => …)
const ok = verifyWebhook(process.env.WEBHOOK_SECRET!, req.header('X-221-Timestamp') ?? '', req.body, req.header('X-221-Signature') ?? '');
if (!ok) return res.sendStatus(401);

Python

Installation
uv add sdk-221   # or: pip install sdk-221, in a virtual environment
payment.py
import os
from sdk221 import client

api = client(os.environ["API_KEY"], os.environ["API_URL"])
response = api.get_httpx_client().post(
    "/v1/payments",
    headers={"Idempotency-Key": "order-1042"},
    json={
        "amount": "5000",
        "currency": "XOF",
        "rail": "sn_wave",
        "merchant_reference": "order-1042",
        "return_url": "https://shop.example/payment/return",
        "customer_phone": "77 123 45 67",
    },
)
response.raise_for_status()
payment = response.json()
webhook.py
from sdk221.webhook import verify_webhook

# FastAPI: raw_body = await request.body()
if not verify_webhook(WEBHOOK_SECRET, request.headers.get("X-221-Timestamp", ""), raw_body, request.headers.get("X-221-Signature", "")):
    raise HTTPException(status_code=401)

Dart and Flutter

Installation
dart pub add sdk221   # or: flutter pub add sdk221
payment.dart
import 'dart:convert';
import 'dart:io';
import 'package:http/http.dart' show MultipartFile;
import 'package:sdk221/sdk221.dart';

Future<void> main() async {
  final env = Platform.environment;
  final client = createApiClient(apiKey: env['API_KEY'], baseUrl: env['API_URL'] ?? defaultBaseUrl);
  final payment = await PaymentsApi(client).paymentsCreate(
    MultipartFile.fromString('body', jsonEncode({
      'amount': '5000',
      'currency': 'XOF',
      'rail': 'sn_wave',
      'merchant_reference': 'order-1042',
      'return_url': 'https://shop.example/payment/return',
      'customer_phone': '77 123 45 67',
    })),
    idempotencyKey: 'order-1042',
  );
  print(payment);
}
webhook.dart
import 'package:sdk221/sdk221.dart';

final ok = verifyWebhook(webhookSecret, request.headers['x-221-timestamp'] ?? '', rawBody, request.headers['x-221-signature'] ?? '');
if (!ok) return Response(401);

Limitation: the generated client imports dart:io. It works on the Dart VM, Android, iOS and desktop, not on Flutter web, where the key must not live anyway.

Offline

The embedded public holidays cover the years present in the data at the time of the release. holidays(2099) throws an error instead of returning an empty list: for another year, call GET /v1/jours-feries?year=.