Transactions
Handling Transaction Webhook Events
Overview
The TRANSACTION webhook fires for every completed money movement on your Sevi wallet: order payments credited to you, daily settlements to your bank, refunds and other wallet operations. Use it for your "money received / money paid out" bookkeeping.
Webhook Event Details
- Event Trigger: Sent when a transaction on your wallet is completed.
- Retry Policy: Up to 3 attempts with exponential backoff if a non-200/201 response is returned.
- Authentication: Each request carries your secret token verbatim in the
Authorizationheader (noBearerprefix; the body is not HMAC-signed). Validate it on every request and reject mismatches with401. See Authorization for the full details, including token rotation. - Idempotency: A transaction can be delivered more than once (retries). Use
idto deduplicate.
Recognising an order payment
In the standard Integration flow the buyer's payment is held by Sevi until delivery is confirmed. You receive exactly one transaction for the order, at the moment your wallet is credited:
| Field | Value |
|---|---|
transactionType | TOP_UP |
status | COMPLETED |
paymentType | ORDER |
orderId | the order id (the order object is included) |
hold.wasHeld | true — the money was held until delivery |
hold.reason | DELIVERY_CONFIRMATION |
hold.releasedAt | when delivery was confirmed and the money was credited |
amount | the (delivered) order amount |
Use these fields — not the description — in your logic. The description is human-readable text (e.g. Payment for order: D123456) and may change.
Recognising a settlement
The daily payout of your wallet balance to your bank account, M-Pesa paybill or till:
| Field | Value |
|---|---|
transactionType | DEDUCT |
paymentType | MONEY_REQUEST |
method | BANK, MPESA_PAYBILL or MPESA_TILL (payout fees arrive as a separate DEDUCT with method FEE) |
orderId | null |
Payload Reference
The webhook sends a JSON body with the transaction fields at the top level, plus optional order and customer objects.
Transaction Fields
| Field | Type | Description |
|---|---|---|
id | string | Transaction ID |
amount | number | Transaction amount |
fee | number | Fee charged |
totalAmount | number | Total amount (amount + fee) |
description | string | null | Human-readable description. Do not parse it. |
paymentType | string | What the movement is for. Common values: ORDER (order payment), MONEY_REQUEST (settlement / payout), WALLET (top-up / transfer), INSTALLMENT (credit repayment), CREDIT, CASHBACK, MEMBERSHIP. |
currency | string | KES, UGX, USD |
transactionType | string | null | Direction on your wallet: TOP_UP (money in) or DEDUCT (money out). |
method | string | null | SEVI (internal, e.g. order credit), MPESA, BANK, MPESA_PAYBILL, MPESA_TILL, FEE, … |
walletId | string | null | Wallet account the transaction belongs to (yours) |
createdAt | datetime | When the transaction was created. For a held order payment this is when the buyer paid; use hold.releasedAt for when you were credited. |
name | string | null | Name / label |
orderId | string | null | Related order ID (if applicable) |
status | string | COMPLETED. The webhook is only sent for completed transactions; you can safely guard on status == "COMPLETED". |
hold | object | Escrow information, see below |
hold
Always present.
| Field | Type | Description |
|---|---|---|
wasHeld | boolean | true when the money was held by Sevi before it was credited (order payments awaiting delivery). false for ordinary transactions. |
reason | string | null | Why it was held: DELIVERY_CONFIRMATION (until the delivery was confirmed) or SETTLEMENT_CONFIRMATION. null when wasHeld is false. |
releasedAt | datetime | null | When the hold was released and your wallet was credited. |
order (optional)
Present when the transaction is linked to an order (orderId is not null). Contains the order information along with shipping and billing details.
| Field | Type | Description |
|---|---|---|
id | string | Order ID |
referenceNumber | string | null | Unique order reference |
platformReference | string | null | Your invoice number |
status | string | null | PENDING, AWAITING_SHIPMENT, DELIVERED, ARCHIVED, CANCELLED, REFUNDED, RETURNED |
paymentStatus | string | PENDING, PARTIALLY_PAID, CREDIT_PROPOSAL_INITIATED, PAID, PARTIALLY_REFUNDED, REFUNDED |
amount | number | Order amount |
currency | string | null | Order currency |
name | string | null | Order name |
phoneNumber | string | null | Customer phone number |
items | json | null | Order line items |
createdAt | datetime | Order creation timestamp |
shipping | object | null | Shipping address (see Order webhook) |
billing | object | null | Billing address (see Order webhook) |
customer (optional)
Present when the transaction is linked to an order that has a customer/vendor relationship. Customer details as known by the vendor.
| Field | Type | Description |
|---|---|---|
name | string | null | Customer name |
email | string | null | Customer email |
reference | string | null | Customer reference in the vendor's system |
phoneNumbers | string[] | Customer phone numbers |
Full Example
An order payment credited after the buyer confirmed delivery:
{
"id": "01234567-abcd-...",
"amount": 5000.00,
"fee": 0.00,
"totalAmount": 5000.00,
"description": "Payment for order: D123456",
"paymentType": "ORDER",
"currency": "KES",
"transactionType": "TOP_UP",
"method": "SEVI",
"walletId": "WAL-vendor-...",
"createdAt": "2025-01-15T09:12:41.000Z",
"name": "Jane Wanjiku",
"orderId": "0b9f6f7e-5a8c-4a57-9a52-3c2f0e1d9a11",
"status": "COMPLETED",
"hold": {
"wasHeld": true,
"reason": "DELIVERY_CONFIRMATION",
"releasedAt": "2025-01-16T14:03:10.000Z"
},
"order": {
"id": "0b9f6f7e-5a8c-4a57-9a52-3c2f0e1d9a11",
"referenceNumber": "D123456",
"platformReference": "INV-1001",
"status": "DELIVERED",
"paymentStatus": "PAID",
"amount": 5000.00,
"currency": "KES",
"name": "Order for Maize flour",
"phoneNumber": "+254712345678",
"items": [
{
"name": "Maize flour 2kg",
"unitPrice": 2500,
"quantity": 2
}
],
"createdAt": "2025-01-15T09:00:00.000Z",
"shipping": {
"firstName": "Jane",
"lastName": "Wanjiku",
"address1": "Kenyatta Avenue 12",
"city": "Nairobi",
"country": "KE",
"phoneNumber": "+254712345678"
},
"billing": {
"firstName": "Jane",
"lastName": "Wanjiku",
"address1": "Kenyatta Avenue 12",
"city": "Nairobi",
"country": "KE",
"phoneNumber": "+254712345678"
}
},
"customer": {
"name": "Jane Wanjiku",
"email": null,
"reference": "CUS018082",
"phoneNumbers": ["+254712345678"]
}
}
The order and customer fields are only present when the transaction is linked to an order. For standalone transactions (e.g. settlements, top-ups), these fields are not included.