Core Concepts → Payment Sessions
Payment Sessions
A payment session is a single, time-bounded payment intent. It holds the amount, payer info, an auto-generated virtual bank account, and a status that updates in real time as the payment is processed. Sessions expire after 15 minutes by default.
Create a session (server-side)
Before opening the payment widget your backend creates a session using your secret key. The response includes a clientSecret (cs_…) that you forward to the frontend — the widget uses it to bootstrap the checkout.
/api/widget/sessionsAuthCreates a payment session and returns a clientSecret for the widget.
Request body
| amount* | integer | Amount in kobo (₦1 = 100 kobo). Minimum: 100 (₦1). |
| currency* | string | ISO 4217 currency code. e.g. "NGN". |
| reference | string | Your idempotency key — auto-generated if omitted. |
| payerName | string | Pre-fills the customer name in the widget. |
| payerEmail | string | Pre-fills the customer email. |
| payerPhone | string | Pre-fills the customer phone number. |
| metadata | object | Arbitrary key-value pairs echoed in webhooks. |
const response = await fetch( // Use dev-api for test keys, api for live keys 'https://dev-api.core.vittasinternational.com/api/widget/sessions', { method: 'POST', headers: { 'Authorization': `Bearer ${process.env.VITTAS_SECRET_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ amount: 500000, // ₦5,000 in kobo currency: 'NGN', reference: 'ORDER-001', payerName: 'Ada Lovelace', payerEmail: 'ada@example.com', payerPhone: '+2348012345678', metadata: { orderId: 'ORD-001' }, }), } ); const { data } = await response.json(); const { sessionId, clientSecret } = data.result; // Forward clientSecret to your frontend — the widget calls VittasPay.init({ clientSecret })
{ "meta": { "statusCode": 201, "message": "Created" }, "data": { "result": { "sessionId": "3f7e2a1b-0000-0000-0000-000000000000", "clientSecret": "cs_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" } } }
Never expose clientSecret beyond the current request
TheclientSecret is single-use and short-lived. Do not log it, store it in a database, or cache it. Return it directly to the frontend response and discard it.Get session status
Poll this endpoint from your backend to check whether a session has been paid. Authenticate with your secret key — no additional headers required.
/api/widget/sessions/:idAuthReturns the current status and key details of a session.
const { data } = await fetch( `https://dev-api.core.vittasinternational.com/api/widget/sessions/${sessionId}`, { headers: { Authorization: `Bearer ${process.env.VITTAS_SECRET_KEY}` }, } ).then(r => r.json()); const session = data.result; console.log(session.status); // 'pending' | 'successful' | 'expired' | ... console.log(session.paidAmount); // amount actually received (null if not yet paid)
{ "meta": { "statusCode": 200, "message": "OK" }, "data": { "result": { "id": "3f7e2a1b-0000-0000-0000-000000000000", "status": "successful", "reference": "ORDER-001", "amount": 500000, "paidAmount": 500000, "currency": "NGN" } } }
Session lifecycle
All sessions start as pending. They transition to a terminal state exactly once, triggered either by an incoming bank transfer or by the expiry timer.
| Status | Description |
|---|---|
| pending | Session created; awaiting payment transfer. |
| successful | Correct amount received and confirmed. |
| mismatch | Transfer received but amount differed from expected. |
| expired | No transfer received before the session TTL elapsed. |
| cancelled | Payer or merchant explicitly cancelled the session. |
| failed | An unrecoverable error occurred during processing. |
Prefer webhooks over polling
Rather than pollingGET /api/widget/sessions/:id, configure a webhook endpoint to receive payment.successful and payment.failed events instantly as they happen. See the Webhooks guide for setup instructions.