SokoHub API Reference
Integrate your store, Zapier, Make (Integromat), or custom backend with SokoHub using our REST API and outbound webhook system.
Base URL
https://api.greatzern.com
API Version
v1 (current)
Format
JSON / UTF-8
Authentication
All seller-authenticated endpoints require a valid session cookie issued by POST /api/auth/login. The cookie is HttpOnly; Secure; SameSite=Lax and is automatically sent by the browser. For server-to-server integrations, include it in the Cookie header.
POST /api/auth/login HTTP/1.1
Host: api.greatzern.com
Content-Type: application/json
{
"email": "seller@example.com",
"password": "your-password"
}
# Response sets:
# Set-Cookie: token=<jwt>; HttpOnly; Secure; Path=/Public storefront endpoints (product listings, order placement, review submission) do not require authentication.
Webhooks
SokoHub sends an HTTP POST to your endpoint URL whenever a subscribed event occurs in your store. All webhook deliveries are signed with HMAC-SHA256 so you can verify they came from SokoHub.
Managing Endpoints
/api/webhooksList all webhook endpoints for your store
/api/webhooksCreate a new webhook endpoint
/api/webhooks/:idGet endpoint details + last 20 deliveries
/api/webhooks/:idUpdate URL, description, or subscribed events
/api/webhooks/:id/toggleEnable or disable an endpoint
/api/webhooks/:idDelete endpoint and all delivery history
/api/webhooks/:id/testSend a test ORDER_CREATED payload
/api/webhooks/:id/deliveriesPaginated delivery log (50/page)
/api/webhooks/eventsList all available event types (public)
Create a Webhook
curl -X POST https://api.greatzern.com/api/webhooks \
-H "Content-Type: application/json" \
-H "Cookie: token=<your-jwt>" \
-d '{
"url": "https://yourapp.com/hooks/sokohub",
"description": "Order + payment events",
"events": ["ORDER_CREATED", "ORDER_PAID", "ORDER_CANCELLED"]
}'{
"id": "wh_01hx...",
"url": "https://yourapp.com/hooks/sokohub",
"description": "Order + payment events",
"events": ["ORDER_CREATED", "ORDER_PAID", "ORDER_CANCELLED"],
"secret": "4a7f2b...8e1d",
"isActive": true,
"createdAt": "2025-06-01T10:00:00.000Z"
}secret is returned only on creation and is never exposed again. Use it to verify the X-SokoHub-Signature header on every incoming request.Webhook Payload Shape
{
"event": "ORDER_CREATED",
"sellerId": "sel_01hz...",
"timestamp": "2025-06-01T12:34:56.789Z",
"data": {
"orderId": "ord_01hz...",
"referenceCode": "SKH-2025-00042",
"status": "PENDING",
"totalKes": 4500,
"customerName": "Amina Wanjiku",
"customerPhone": "+254712345678",
"itemCount": 3
}
}Retry Policy
SokoHub makes up to 3 delivery attempts per event. Attempts are made with a 10-second timeout. Your endpoint must return a 2xx response within that window to be counted as a success. Delivery results are logged and visible via the deliveries endpoint.
Webhook Events
| Event | Description |
|---|---|
ORDER_CREATED | A new order has been placed on the storefront. |
ORDER_PAID | M-Pesa payment confirmed for an order. |
ORDER_CONFIRMED | Seller manually confirmed the order. |
ORDER_DISPATCHED | Order has been dispatched for delivery. |
ORDER_DELIVERED | Order marked as delivered. |
ORDER_CANCELLED | Order was cancelled. |
PRODUCT_CREATED | A new product was added to the catalogue. |
PRODUCT_UPDATED | An existing product was edited. |
PRODUCT_DELETED | A product was removed. |
CUSTOMER_CREATED | A new customer record was created. |
PAYMENT_RECEIVED | Any M-Pesa payment received (orders or subscriptions). |
LOW_STOCK_ALERT | Product stock fell below the alert threshold. |
REVIEW_SUBMITTED | A customer submitted a product review. |
Signature Verification
Every webhook request includes an X-SokoHub-Signature header formatted as sha256=<hex-digest>. Verify it by computing HMAC-SHA256 of the raw request body with your endpoint secret.
Node.js / TypeScript
import { createHmac, timingSafeEqual } from 'crypto';
function verifySignature(
rawBody: string | Buffer,
signatureHeader: string,
secret: string
): boolean {
const expected = 'sha256=' + createHmac('sha256', secret)
.update(rawBody)
.digest('hex');
const a = Buffer.from(signatureHeader);
const b = Buffer.from(expected);
if (a.length !== b.length) return false;
return timingSafeEqual(a, b);
}
// Express / Fastify example
app.post('/hooks/sokohub', express.raw({ type: 'application/json' }), (req, res) => {
const sig = req.headers['x-sokohub-signature'] as string;
if (!verifySignature(req.body, sig, process.env.SOKOHUB_WEBHOOK_SECRET!)) {
return res.status(401).json({ error: 'Invalid signature' });
}
const event = JSON.parse(req.body.toString());
// Handle event...
res.sendStatus(200);
});Python
import hmac, hashlib
def verify_signature(raw_body: bytes, signature_header: str, secret: str) -> bool:
expected = 'sha256=' + hmac.new(
secret.encode(), raw_body, hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature_header)
# Flask example
from flask import Flask, request, abort
app = Flask(__name__)
@app.route('/hooks/sokohub', methods=['POST'])
def webhook():
sig = request.headers.get('X-SokoHub-Signature', '')
if not verify_signature(request.get_data(), sig, SOKOHUB_SECRET):
abort(401)
event = request.get_json(force=True)
# Handle event...
return '', 200Products API
/api/productsList all products (paginated, filterable)
/api/productsCreate product with image (multipart/form-data)
/api/products/bulkBulk create from JSON (CSV import)
/api/products/:idGet single product with variants
/api/products/:idUpdate product fields
/api/products/:idDelete product
/api/products/:id/variantsAdd a variant to a product
/api/products/:id/variants/:vIdUpdate a variant
/api/products/:id/variants/:vIdDelete a variant
# List products curl https://api.greatzern.com/api/products?page=1&limit=20 \ -H "Cookie: token=<jwt>" # Get a single product curl https://api.greatzern.com/api/products/prod_01hz... \ -H "Cookie: token=<jwt>"
Orders API
/api/ordersList orders (auth required)
/api/orders/:idGet order details with items and timeline
/api/ordersPlace a new order (public — storefront)
/api/orders/:id/confirmConfirm an order
/api/orders/:id/dispatchMark order as dispatched
/api/orders/:id/deliverMark order as delivered
/api/orders/:id/cancelCancel an order
// Order object
{
"id": "ord_01hz...",
"referenceCode": "SKH-2025-00042",
"status": "PAID",
"totalKes": 4500,
"customerName": "Amina Wanjiku",
"customerPhone": "+254712345678",
"deliveryMethod": "DELIVERY",
"mpesaReceipt": "QGH1234X5Y",
"paidAt": "2025-06-01T12:36:00.000Z",
"items": [
{
"productName": "African Print Tote Bag",
"quantity": 2,
"unitPriceKes": 1500,
"subtotalKes": 3000
}
]
}Customers API
/api/customersList all customers for your store
/api/customers/:idGet customer profile, orders, and stats
/api/customers/:idUpdate customer information
/api/customers/:id/ordersList all orders for a customer
Analytics API
/api/analytics/overviewRevenue, order counts, conversion rate
/api/analytics/revenueRevenue time series (daily/weekly/monthly)
/api/analytics/top-productsBest-selling products
/api/analytics/customersCustomer acquisition and retention stats
All analytics endpoints accept a ?period=7d|30d|90d query parameter.
Error Reference
All errors return JSON with success: false and a human-readable message.
| HTTP | Code | Meaning |
|---|---|---|
| 400 | VALIDATION_ERROR | Request body failed schema validation |
| 401 | UNAUTHORIZED | Missing or invalid auth token |
| 403 | FORBIDDEN | Authenticated but insufficient permissions |
| 404 | NOT_FOUND | Resource does not exist or is not yours |
| 409 | CONFLICT | Duplicate resource (e.g. duplicate receipt) |
| 429 | RATE_LIMITED | Too many requests — back off and retry |
| 500 | INTERNAL_ERROR | Unexpected server error |
// Error response shape
{
"success": false,
"message": "Product not found",
"statusCode": 404
}Ready to integrate?
Open your dashboard, navigate to Settings → Integrations, and create your first webhook endpoint in seconds.
Go to Dashboard