Payment Expiry
How long payment sessions stay open, what happens when they expire, and how to handle expired payments in your webhook listener.
Overview
When you create a Direct Payment or Hosted Checkout session, LakiPay keeps the payment active for 15 minutes. That window gives the customer enough time to approve a USSD push, finish on the hosted page, or complete their bank or wallet flow.
If the customer does not complete payment within that limit, the session expires automatically. Your backend should treat expiry as a terminal failure for that attempt — do not fulfill the order until you receive a successful status for a new payment.
Expiry lifecycle
Create payment
Your backend calls POST /api/v2/payment/direct or POST /api/v2/payment/checkout. The transaction starts as PENDING.
Customer completes or abandons
The customer receives a prompt (or is redirected to hosted checkout). They may pay successfully, fail, cancel, or do nothing.
Payment expiry
If payment is not completed within 15 minutes, LakiPay expires the session. The attempt is no longer payable.
Webhook notification
LakiPay notifies your server (for example payment.expired / DEPOSIT with a terminal non-success status such as CANCELLED). Update your order state and optionally invite a retry.
See the visual payment and webhook walkthrough on /integration.
Handling expired payments
Required for production
Always handle expiry (and other terminal non-success statuses) in your webhook listener. Leaving orders open indefinitely causes duplicate retries, stuck inventory, and inconsistent customer messaging.
What to do when a payment expires
- Mark the order or invoice as expired / unpaid — never treat PENDING as paid after the timeout window.
- Notify the customer that the session timed out and offer a new payment (new unique
reference). - Optionally confirm with GET /api/v2/payment/transaction/{id} or by reference before showing final UI state.
- Verify the webhook signature before acting — see Webhook signature verification.
Example webhook handler
Pattern from the Integration guide: treat failure and cancellation/expiry as terminal, and only fulfill on SUCCESS.
// Verify RSA-2048 signature first (see /docs/webhooks)
app.post('/webhooks/lakipay', (req, res) => {
const { event, transaction_id, reference, status } = req.body;
if (event === 'DEPOSIT') {
if (status === 'SUCCESS') {
// Payment completed – fulfill order, update DB
} else if (status === 'FAILED' || status === 'CANCELLED') {
// Expired / failed / cancelled – release hold, notify customer
// Optionally create a new payment with a fresh reference
}
}
res.status(200).send('OK');
});Restarting after expiry
After expiry, create a new payment request. Reuse of the same reference may be rejected or confuse reconciliation — generate a new unique reference for each attempt.