Overview
Create hosted checkout pages for your customers
Payment Sessions provide a hosted checkout experience for collecting payments. Create a session, send the checkout URL to your customer, and they complete payment on Snippe's secure checkout page.
Sessions are ideal when you want a pre-built, mobile-optimized checkout UI without building your own payment form.
Payment Profiles
Reusable templates with branding and default settings.
Payment Links
Short, shareable URLs for easy payment collection.
How It Works
Create Session
Call the API with amount and configuration to get a checkout URL.
Share Checkout URL
Send the checkout_url to your customer via email, SMS, WhatsApp, or embed it in your app.
Customer Pays
Customer opens the link, chooses a payment method, and completes payment on Snippe's secure checkout page.
Receive Webhook
You receive a payment.completed webhook when payment succeeds.
API Endpoints
Merchant Endpoints
All merchant endpoints require authentication via Authorization: Bearer <token> (JWT) or X-API-Key: <api_key>.
| Endpoint | Method | Description |
|---|---|---|
/api/v1/sessions | POST | Create session |
/api/v1/sessions | GET | List sessions |
/api/v1/sessions/:reference | GET | Get session details |
/api/v1/sessions/:reference/cancel | POST | Cancel session |
Public Endpoints
These endpoints power the hosted checkout page and do not require authentication.
| Endpoint | Method | Description |
|---|---|---|
/checkout/:token | GET | Load checkout page |
/checkout/:token/pay | POST | Submit payment |
/checkout/:token/status | GET | Poll payment status |
/checkout/:token/cancel/:attemptID | POST | Cancel payment attempt |
/p/:code | GET | Short link redirect |
Create Session
POST /api/v1/sessions
Authorization: Bearer <token> # or X-API-Key: <api_key>
Content-Type: application/jsonRequest Body
| Field | Type | Required | Default | Notes |
|---|---|---|---|---|
amount | uint64 | Yes (fixed) / No (custom) | — | Min 500. Suggested amount when allow_custom_amount is true |
currency | string | No | TZS | ISO 4217 |
allowed_methods | string[] | No | ["mobile_money", "qr"] | mobile_money, qr, card |
allow_custom_amount | bool | No | false | Customer enters own amount at checkout |
min_amount | uint64 | Yes (when custom) | — | Min 500, must be < max_amount |
max_amount | uint64 | Yes (when custom) | — | Must be > min_amount |
customer | object | No | — | Pre-fills checkout form |
profile_id | string | No | User's default | Payment profile for branding |
redirect_url | string | No | — | Where to send customer after payment. Max 500 chars |
webhook_url | string | No | — | Receives payment events. Max 500 chars |
description | string | No | — | Max 500 chars |
metadata | object | No | — | Max 50 keys, custom key-value data |
expires_in | int | No | 3600 | Seconds. Range: 60–86400 |
line_items | array | No | — | Max 50 items. Display-only |
custom_fields | array | No | — | Max 20 fields |
display | object | No | — | Checkout UI settings |
billing | object | No | — | Pre-fills card billing at checkout |
Branding (merchant_name, merchant_logo, brand_color, locale,
collect_email) comes from the payment profile linked via profile_id — not
from the request body.
Payment Methods
| Value | Description |
|---|---|
mobile_money | Mobile money (Airtel, M-Pesa, Mixx by Yas, Halotel) |
qr | QR code payments |
card | Credit/debit card payments |
Customer Object
| Field | Type | Notes |
|---|---|---|
name | string | Max 200 chars |
phone | string | Customer phone number |
email | string | Valid email format |
Basic Example
{
"amount": 50000,
"currency": "TZS",
"allowed_methods": ["mobile_money", "qr"],
"customer": {
"name": "John Doe",
"phone": "+255712345678",
"email": "john@example.com"
},
"redirect_url": "https://yoursite.com/success",
"webhook_url": "https://yoursite.com/webhooks/snippe",
"description": "Order #12345",
"metadata": {
"order_id": "12345"
},
"expires_in": 3600
}With Profile
{
"profile_id": "prof_550e8400-e29b-41d4-a716-446655440000",
"amount": 50000,
"description": "Order #12345"
}Response
{
"code": 201,
"data": {
"id": "uuid",
"reference": "sess_abc123def456",
"status": "pending",
"mode": "payment",
"amount": 50000,
"currency": "TZS",
"checkout_url": "https://snippe.me/checkout/W0SzdUSHQm",
"short_code": "Ax7kM2",
"payment_link_url": "https://snippe.me/p/Ax7kM2",
"merchant_name": "Acme Store",
"merchant_logo": "https://example.com/logo.png",
"brand_color": "#4F46E5",
"locale": "en",
"customer_name": "John Doe",
"customer_phone": "+255712345678",
"customer_email": "john@example.com",
"allowed_methods": ["mobile_money", "qr", "card"],
"redirect_url": "https://yoursite.com/success",
"webhook_url": "https://yoursite.com/webhooks/snippe",
"description": "Order #12345",
"metadata": { "order_id": "12345" },
"allow_custom_amount": false,
"min_amount": 0,
"max_amount": 0,
"line_items": [],
"custom_fields": [],
"display": {
"show_line_items": true,
"line_items_style": "compact",
"show_description": true,
"show_merchant_logo": true,
"theme": "light",
"success_message": "Payment successful!",
"button_text": "Pay Now"
},
"billing": {},
"collect_email": false,
"expires_at": "2026-02-26T11:00:00Z",
"created_at": "2026-02-26T10:00:00Z"
}
}Line Items
Display cart items on the checkout page. Max 50 items per session.
| Field | Type | Required | Notes |
|---|---|---|---|
id | string | No | Your item identifier |
name | string | Yes | Item name |
description | string | No | Item description |
image_url | string | No | Product image URL |
quantity | int | Yes | Number of items |
unit_price | uint64 | Yes | Price per unit (smallest currency unit) |
sku | string | No | SKU code |
category | string | No | Category label |
{
"amount": 150000,
"line_items": [
{
"id": "item_001",
"name": "Blue T-Shirt",
"description": "100% Cotton",
"image_url": "https://shop.com/tshirt.jpg",
"quantity": 2,
"unit_price": 25000,
"sku": "TSH-BLU-M",
"category": "Apparel"
},
{
"name": "Shipping",
"quantity": 1,
"unit_price": 50000
}
],
"display": {
"show_line_items": true,
"line_items_style": "compact"
}
}Custom Fields
Collect additional information from customers. Max 20 fields per session.
| Field | Type | Required | Notes |
|---|---|---|---|
key | string | Yes | Alphanumeric + underscore |
label | string | Yes | Display label |
type | string | Yes | text, email, phone, number, date, select, textarea |
required | bool | Yes | Whether field is required |
placeholder | string | No | Placeholder text |
options | string[] | No | Options for select type |
{
"custom_fields": [
{
"key": "delivery_address",
"label": "Delivery Address",
"type": "textarea",
"required": true
},
{
"key": "delivery_option",
"label": "Delivery Option",
"type": "select",
"options": ["Standard", "Express", "Pickup"],
"required": true
}
]
}Display Settings
| Field | Type | Default | Notes |
|---|---|---|---|
show_line_items | bool | false | Show cart items on checkout |
line_items_style | string | "compact" | "compact" or "cards" |
show_description | bool | false | Show description on checkout |
show_merchant_logo | bool | false | Show merchant logo |
theme | string | — | "light" or "dark" |
success_message | string | — | Shown after payment |
button_text | string | — | Pay button label |
Metadata
Store custom key-value data with your session for reconciliation and tracking.
| Constraint | Limit |
|---|---|
| Max keys | 50 |
| Key length | 40 characters |
| Value length | 500 characters |
| Allowed types | string, number, boolean |
{
"amount": 50000,
"metadata": {
"order_id": "ORD-12345",
"customer_id": "cust_789",
"source": "mobile_app",
"campaign": "summer_sale"
}
}Metadata is returned in session responses and webhook payloads, making it easy to match payments to your internal records.
Billing Object (Card Payments)
Pre-fill billing details to skip the billing form at checkout.
| Field | Type | Required | Notes |
|---|---|---|---|
first_name | string | Yes | Cardholder first name |
last_name | string | Yes | Cardholder last name |
address | string | Yes | Billing street address |
city | string | Yes | Billing city |
state | string | Yes | Billing state/region |
postcode | string | Yes | Billing postal/zip code |
country | string | Yes | ISO 3166-1 alpha-2 (e.g., TZ, KE) |
phone | string | No | Billing phone number |
If billing is pre-filled and card is in allowed_methods, customers won't
need to enter billing details at checkout.
Session Status
| Status | Description |
|---|---|
pending | Session created, awaiting payment |
active | Payment in progress |
completed | Payment successful |
expired | Session expired before payment |
cancelled | Session cancelled |
Get Session
GET /api/v1/sessions/:reference
Authorization: Bearer <token> # or X-API-Key: <api_key>Returns the same shape as the create session response with status code 200.
List Sessions
GET /api/v1/sessions?limit=20&offset=0&status=completed
Authorization: Bearer <token> # or X-API-Key: <api_key>| Parameter | Type | Default | Notes |
|---|---|---|---|
limit | int | 20 | Max 100 |
offset | int | 0 | Pagination offset |
status | string | — | pending, active, completed, expired, cancelled |
Response
{
"code": 200,
"data": [
{ /* SessionResponse */ }
],
"meta": {
"total": 42,
"limit": 20,
"offset": 0
}
}Cancel Session
POST /api/v1/sessions/:reference/cancel
Authorization: Bearer <token> # or X-API-Key: <api_key>Response
{
"code": 200,
"message": "session cancelled"
}Only sessions in pending or active status can be cancelled. Returns 404
if not found, 400 if already in a final state.
Checkout Endpoints (Public)
These endpoints power the hosted checkout page and do not require authentication.
Load Checkout Page
GET /checkout/:tokenReturns session details for rendering the checkout UI.
{
"code": 200,
"data": {
"reference": "sess_abc123def456",
"status": "pending",
"amount": 50000,
"currency": "TZS",
"merchant_name": "Acme Store",
"merchant_logo": "https://example.com/logo.png",
"brand_color": "#4F46E5",
"locale": "en",
"customer_name": "John Doe",
"customer_phone": "+255712345678",
"customer_email": "john@example.com",
"allowed_methods": ["mobile_money", "qr", "card"],
"description": "Order #12345",
"collect_email": false,
"allow_custom_amount": false,
"min_amount": 0,
"max_amount": 0,
"line_items": [],
"custom_fields": [],
"display": {},
"expires_at": "2026-02-26T11:00:00Z"
}
}| Status | Description |
|---|---|
| 404 | Token not found |
| 410 | Session expired, completed, or cancelled |
Submit Payment
POST /checkout/:token/pay
Content-Type: application/json| Field | Type | Required | Notes |
|---|---|---|---|
payment_method | string | Yes | mobile_money, qr, card |
amount | uint64 | Only when allow_custom_amount | Must be between min_amount and max_amount |
customer_name | string | No | |
customer_phone | string | Yes (mobile_money) | |
customer_email | string | No | |
billing | object | Yes (card) | Unless pre-filled at session creation |
Response
{
"code": 200,
"data": {
"attempt_id": "attempt_xyz789",
"status": "pending",
"payment_url": "https://gateway.com/pay?token=abc",
"qr_code": "data:image/png;base64,...",
"payment_token": "token_abc123",
"expires_at": "2026-02-26T10:05:00Z"
}
}Only one of payment_url, qr_code, or payment_token is populated
depending on the payment method.
Poll Payment Status
GET /checkout/:token/status{
"code": 200,
"data": {
"reference": "sess_abc123def456",
"status": "completed",
"redirect_url": "https://yoursite.com/success"
}
}Poll every 2–3 seconds. Redirect the customer when status is "completed".
Cancel Payment Attempt
POST /checkout/:token/cancel/:attemptID{
"code": 200,
"message": "payment attempt cancelled"
}Cancels a specific payment attempt. The session remains open for the customer to try again.
Example Flows
Fixed Amount (E-commerce)
{
"amount": 50000,
"description": "Order #12345"
}Custom Amount (Donation)
{
"allow_custom_amount": true,
"min_amount": 1000,
"max_amount": 500000,
"description": "Donation"
}Custom Amount with Suggested Default
{
"allow_custom_amount": true,
"amount": 10000,
"min_amount": 1000,
"max_amount": 500000
}Rich Checkout (Line Items + Display)
{
"amount": 150000,
"line_items": [
{
"name": "Widget",
"quantity": 2,
"unit_price": 75000
}
],
"display": {
"show_line_items": true,
"line_items_style": "cards",
"button_text": "Buy Now"
}
}Donation with Tiers
{
"allow_custom_amount": true,
"min_amount": 5000,
"max_amount": 1000000,
"line_items": [
{
"id": "tier_1",
"name": "1 Meal",
"unit_price": 5000,
"quantity": 1
},
{
"id": "tier_2",
"name": "5 Meals",
"unit_price": 25000,
"quantity": 1
}
],
"display": {
"show_line_items": true,
"line_items_style": "cards",
"button_text": "Donate Now"
}
}Webhooks
When payment completes, you receive a webhook at your webhook_url:
{
"type": "payment.completed",
"data": {
"reference": "pi_xyz789",
"session_reference": "sess_abc123def456",
"status": "completed",
"amount": {
"value": 50000,
"currency": "TZS"
},
"customer": {
"name": "John Doe",
"phone": "+255712345678"
},
"payment_method": "mobile_money",
"completed_at": "2026-01-29T14:35:00Z"
}
}See Webhooks for signature verification.
Error Responses
| Status | Code | Description |
|---|---|---|
| 400 | validation_error | Invalid request data |
| 400 | invalid_state | Session already in final state |
| 404 | not_found | Session or token not found |
| 409 | conflict | Payment already in progress |
| 410 | payment_expired | Session has expired |
Best Practices
Always Configure Webhooks
Don't rely solely on redirect URLs. Webhooks ensure you're notified even if the customer closes their browser.
Use Payment Profiles
Create profiles for consistent branding across all checkouts.
Pre-fill Customer Data
When you have customer info, pass it in the customer object to speed up checkout.
Include Metadata
Store your internal order ID in metadata for easy reconciliation.