Status Codes Reference
Complete reference for all transaction status codes in the FLUID Network API. Understanding these statuses is crucial for tracking transaction lifecycle and building robust integrations.
Quick Reference
| Status | Description | Final State? | Customer Action Required? |
|---|---|---|---|
pending | Transaction queued for processing | No | Wait |
processing | Bank is processing the transaction | No | Wait |
completed | Transaction successful | ✅ Yes | None |
failed | Transaction failed | ✅ Yes | Retry or check account |
reversed | Transaction refunded | ✅ Yes | None (informational) |
Transaction Status Flow
Valid Transitions: Status transitions must follow the flow above. Attempting invalid transitions (e.g., pending → completed) will result in error code 3001 (Invalid status transition).
Status Definitions
pending
Description
Transaction has been created and validated, awaiting submission to the bank's API.
Typical Duration
- 1-5 seconds under normal load
- Up to 30 seconds during high traffic periods
When This Status Occurs
- Transaction passes API validation
- Transaction is queued in internal processing system
- Waiting for bank connector to pick up the transaction
Webhook Events
transaction.createdtransaction.pending
What Your System Should Do
- Display "Payment initiated" or "Processing" to customer
- Store transaction reference for tracking
- Do not fulfill orders yet
- Do not mark payment as complete
Expected Next Status
processing(normal flow)failed(if validation fails before bank submission)
Example Response
{
"success": true,
"data": {
"id": "dt_12345",
"reference": "FLU-20250128-ABC123",
"status": "pending",
"amount": 100.00,
"currency": "GHS",
"created_at": "2025-01-28T10:15:00Z",
"processed_at": null,
"completed_at": null
}
}processing
Description
Transaction has been successfully submitted to the bank and is being processed. The bank has received the request and is awaiting customer approval or performing internal checks.
Typical Duration
- USSD approval: 30 seconds - 2 minutes (customer must approve)
- Direct debit: 5-30 seconds (automated)
- Web/App approval: 1-5 minutes (customer interaction required)
When This Status Occurs
- Bank API accepts the transaction request
- Bank returns a
bank_reference(tracking ID) - For USSD: Customer receives USSD prompt on their phone
- For direct debit: Bank validates account and processes automatically
Webhook Events
transaction.processing
What Your System Should Do
- Update UI to "Awaiting bank confirmation"
- For USSD: Instruct customer to check phone for prompt
- Display estimated completion time (2-3 minutes)
- Do not fulfill orders yet
- Monitor for webhook completion events
Expected Next Status
completed(successful bank confirmation)failed(bank rejection, timeout, or customer decline)
Example Response
{
"success": true,
"data": {
"id": "dt_12345",
"reference": "FLU-20250128-ABC123",
"status": "processing",
"amount": 100.00,
"currency": "GHS",
"bank_reference": "BNK-REF-789012",
"approval_method": "ussd",
"created_at": "2025-01-28T10:15:00Z",
"processed_at": "2025-01-28T10:15:12Z",
"completed_at": null
}
}Bank Reference Available: Once a transaction reaches processing, the bank_reference field is populated. Use this for bank-level inquiries or disputes.
completed ✅
Description
Final success state. The bank has confirmed successful processing. Funds have been debited from the customer's account.
When This Status Occurs
- Bank sends success callback or confirmation
- Customer approves USSD prompt (for USSD approval method)
- Bank validates and processes direct debit successfully
- All bank-side validations pass
Webhook Events
transaction.completed
What Your System Should Do ✅
- Mark payment as successful
- Fulfill order or deliver service
- Send confirmation to customer (SMS/email)
- Update inventory (if applicable)
- Generate receipt
- Update accounting records
⚠️ This Is Your Trigger: completed status is the ONLY status where you should fulfill orders or mark payments as successful. Do not fulfill on any other status.
Example Response
{
"success": true,
"data": {
"id": "dt_12345",
"reference": "FLU-20250128-ABC123",
"status": "completed",
"amount": 100.00,
"currency": "GHS",
"fee": 1.50,
"bank_reference": "BNK-REF-789012",
"approval_method": "ussd",
"created_at": "2025-01-28T10:15:00Z",
"processed_at": "2025-01-28T10:15:12Z",
"completed_at": "2025-01-28T10:15:45Z"
}
}Typical Timeline
| Phase | Duration | Cumulative |
|---|---|---|
pending → processing | 2-5 seconds | 2-5s |
processing → completed | 30s - 2 minutes | 32s - 2m 5s |
| Total | 30s - 2 minutes | 30s - 2m 5s |
failed ❌
Description
Final failure state. Transaction processing failed at some point. No funds were debited from the customer.
Common Failure Reasons
| Reason | Error Code | Description | Can Retry? |
|---|---|---|---|
| Insufficient funds | 3009 | Customer account balance too low | ✅ Yes (after customer adds funds) |
| Transaction declined | 3002 | Customer declined USSD prompt | ✅ Yes (customer can retry approval) |
| Invalid account | - | Phone number or account invalid | ❌ No (fix account details first) |
| Timeout | 2408 | Bank or network timeout | ✅ Yes (retry immediately) |
| Bank API error | 2002 | Bank system unavailable or error | ✅ Yes (retry with exponential backoff) |
| Duplicate reference | 3003 | Transaction with this reference already exists | ❌ No (use existing transaction) |
| Amount limit exceeded | 3005 | Amount exceeds configured limits | ❌ No (reduce amount) |
When This Status Occurs
- Customer declines USSD prompt
- Insufficient funds in customer account
- Invalid phone number or account details
- Bank system timeout or error
- Network connectivity issues
- Transaction validation fails
Webhook Events
transaction.failed
What Your System Should Do ❌
- Do NOT fulfill order
- Display error message to customer
- Log failure reason from
error_messagefield - Suggest remediation based on error code (see table above)
- Allow retry for retryable errors
- Update transaction status to failed
Example Response
{
"success": true,
"data": {
"id": "dt_12346",
"reference": "FLU-20250128-DEF456",
"status": "failed",
"amount": 500.00,
"currency": "GHS",
"error_message": "Insufficient funds in customer account",
"bank_reference": "BNK-REF-789013",
"created_at": "2025-01-28T10:15:50Z",
"processed_at": "2025-01-28T10:16:05Z",
"completed_at": null
}
}Handling Failed Transactions:
- Parse the
error_messagefield for user-friendly error details - Check Error Codes Reference for specific error code handling
- For retryable errors (insufficient funds, timeout), allow customer to retry with same reference
- For non-retryable errors (invalid account), prompt customer to update details before retrying
reversed
Description
Final state. A previously completed transaction has been reversed (refunded). Funds have been returned to the customer's account.
When This Status Occurs
- Merchant initiates a refund
- Bank reverses transaction due to error or fraud
- Dispute resolution requires reversal
- Regulatory or compliance requirement
Typical Timeline
- Reversal request: Instant
- Funds return to customer: 1-5 business days (bank-dependent)
Webhook Events
transaction.reversed
What Your System Should Do
- Reverse order fulfillment (if possible)
- Update accounting records to reflect refund
- Send reversal notification to customer
- Mark order as refunded
- Update inventory (restore items if applicable)
Example Response
{
"success": true,
"data": {
"id": "dt_12345",
"reference": "FLU-20250128-ABC123",
"status": "reversed",
"amount": 100.00,
"currency": "GHS",
"bank_reference": "BNK-REF-789012",
"metadata": {
"reversal_reason": "Customer requested refund"
},
"created_at": "2025-01-28T10:15:00Z",
"processed_at": "2025-01-28T10:15:12Z",
"completed_at": "2025-01-28T10:15:45Z"
}
}Reversal Metadata: Reversed transactions include a reversal_reason in the metadata field explaining why the reversal occurred. This is informational only and not standardized.
Status Transition Rules
Valid Transitions
| From | To | Allowed? | Description |
|---|---|---|---|
pending | processing | ✅ Yes | Normal flow - submitted to bank |
pending | failed | ✅ Yes | Validation failed before bank submission |
processing | completed | ✅ Yes | Bank confirms success |
processing | failed | ✅ Yes | Bank rejects or times out |
completed | reversed | ✅ Yes | Refund initiated |
Invalid Transitions
| From | To | Allowed? | Error Code |
|---|---|---|---|
pending | completed | ❌ No | 3001 |
pending | reversed | ❌ No | 3001 |
processing | pending | ❌ No | 3001 |
completed | pending | ❌ No | 3001 |
completed | processing | ❌ No | 3001 |
completed | failed | ❌ No | 3001 |
failed | completed | ❌ No | 3001 |
failed | processing | ❌ No | 3001 |
reversed | Any | ❌ No | 3001 |
Status Finality: The statuses completed, failed, and reversed are terminal states. Once a transaction reaches these states, it cannot transition to any other status (except completed → reversed for refunds).
Querying by Status
API Endpoint
GET /api/v1/payment-providers/debit-requests?status=completedExample Queries
To filter transactions by status, add the status query parameter to the API endpoint:
GET /api/v1/payment-providers/debit-requests?status=completed
GET /api/v1/payment-providers/debit-requests?status=failed
GET /api/v1/payment-providers/debit-requests?status=processingImplementation approach:
- Send GET request to the debit requests endpoint
- Include
statusquery parameter with desired status value - Include authentication header with your API key
- Parse the JSON response containing matching transactions
Common use cases:
- Get all completed transactions for reconciliation
- Monitor failed transactions for customer support
- Track processing transactions to identify stuck payments
Best Practices
1. Use Webhooks Instead of Polling
❌ Inefficient approach: Polling for status changes
- Repeatedly queries the API in a loop to check if status changed
- Causes unnecessary API calls and hits rate limits
- Adds latency (e.g., 5-second polling interval means 5-second delay in detecting changes)
- Wastes server resources and bandwidth
✅ Recommended approach: Subscribe to webhooks
- FLUID sends real-time notifications when status changes
- No polling required - instant updates
- Reduces API calls by 95%+
- More efficient and reliable
Implementation:
- Configure a webhook endpoint on your server (e.g.,
POST /webhooks/fluid) - Subscribe to
transaction.completedandtransaction.failedevents in the dashboard - Verify webhook signatures for security
- Return HTTP 200 response quickly (process async)
2. Only Fulfill on completed Status
✅ Correct approach:
- Wait for
completedstatus before fulfilling orders - Never fulfill on
pendingorprocessingstatus - Handle each status appropriately:
completed: Fulfill order, send confirmation, update accountingfailed: Notify customer, log reason, allow retryprocessing: Update UI to "awaiting confirmation"pending: Show "processing" indicatorreversed: Reverse fulfillment, refund customer, update inventory
❌ Common mistake:
- Fulfilling orders when status reaches
processing - This is incorrect because the transaction may still fail
- Customer gets the product/service but payment fails
- Results in revenue loss and reconciliation issues
Why this matters:
completedis the ONLY status that guarantees funds were debited- Bank may reject transaction even after
processingstatus - Customer may decline USSD prompt or have insufficient funds
3. Handle Failed Transactions Gracefully
When a transaction fails, provide user-friendly error messages and appropriate retry options:
Implementation approach:
- Parse error message from
transaction.error_messagefield - Map to user-friendly text - technical errors should be translated to plain language
- Provide context - explain what went wrong and what the customer should do
- Enable retry - for retryable errors (insufficient funds, timeout), show retry button
- Block retry - for non-retryable errors (invalid account), prompt to fix details first
Error message mapping examples:
Insufficient funds→ "Your account balance is too low. Please add funds and try again."Transaction declined→ "You declined the payment prompt. Would you like to try again?"Timeout→ "The transaction timed out. Please try again."Invalid account→ "We couldn't find your account. Please verify your phone number."
Retry logic:
- Allow retry for: insufficient funds, timeout, network errors, declined approval
- Prevent retry for: invalid account, amount exceeds limit, duplicate reference
- Use the same reference when retrying (for idempotency)
4. Store Status History
Track all status changes in your database for auditing, debugging, and analytics:
Benefits:
- Audit trail: Complete history of transaction state changes
- Debugging: Identify when and why status changed
- Analytics: Measure conversion rates, failure patterns, and processing times
- Reconciliation: Verify transaction flow matches expected patterns
Data to store:
- Transaction ID and reference
- Previous status
- New status
- Timestamp of change
- Webhook event type
- Any error messages or metadata
Use cases:
- Investigate customer complaints about failed payments
- Analyze average time spent in
processingstatus - Identify patterns in failed transactions
- Generate reports on transaction success rates
- Debug webhook delivery issues
5. Set Timeouts for Long-Running Transactions
Monitor transactions that remain in processing status for too long:
Why this matters:
- Normal processing time: 30 seconds - 2 minutes
- If stuck >5 minutes, likely an issue (network timeout, webhook missed, bank system issue)
- Proactive monitoring helps identify problems early
Implementation approach:
- Query database periodically (e.g., every 5 minutes) for transactions in
processingstatus - Filter by time - find transactions where
processed_atis older than 5 minutes - Fetch latest status from FLUID API to check if webhook was missed
- Update local record if status changed remotely
- Alert support if still stuck after verification
Monitoring thresholds:
processing> 5 minutes → Check status via APIprocessing> 10 minutes → Alert support teampending> 2 minutes → Check for system issues
Recovery actions:
- If webhook was missed, update local status from API response
- If truly stuck, contact support with transaction reference
- Notify customer about delay if stuck >5 minutes
Status Code Comparison
FLUID vs Other Payment Processors
| FLUID Status | Provider A | Provider B | Provider C | Description |
|---|---|---|---|---|
pending | requires_action | CREATED | pending | Awaiting processing |
processing | processing | PENDING | ongoing | Bank is processing |
completed | succeeded | COMPLETED | success | Successfully processed |
failed | failed | FAILED | failed | Processing failed |
reversed | refunded | REFUNDED | reversed | Refunded/reversed |
Related Webhook Events
Each status has corresponding webhook events:
| Status | Webhook Event | Description |
|---|---|---|
pending | transaction.pending | Transaction queued for processing |
processing | transaction.processing | Submitted to bank, awaiting response |
completed | transaction.completed | Transaction successful ✅ |
failed | transaction.failed | Transaction failed ❌ |
reversed | transaction.reversed | Transaction refunded |
See Webhook Event Types for complete event documentation.
Troubleshooting
Transaction Stuck in pending
Symptoms: Transaction status remains pending for >1 minute
Possible Causes:
- High system load
- Bank connector queue backlog
- Network connectivity issues
Solutions:
- Wait 2-3 minutes before escalating
- Contact support with transaction reference if stuck >5 minutes
Transaction Stuck in processing
Symptoms: Transaction status remains processing for >5 minutes
Possible Causes:
- Customer hasn't approved USSD prompt
- Bank system slow or unresponsive
- Network timeout
Solutions:
- For USSD: Verify customer received prompt and remind them to approve
- Wait 5 minutes before escalating
- Fetch latest status via API:
GET /debit-requests/:reference - If stuck >10 minutes, contact support
Unexpected Status Transition
Symptoms: Status transition doesn't match expected flow
Example: Transaction goes from pending directly to completed (skipping processing)
Explanation: This is not possible - you may be:
- Missing webhook events (check webhook logs)
- Caching old status (refresh from API)
- Experiencing a race condition (webhooks delivered out of order)
Solutions:
- Always use
previous_statusfield in webhooks to detect missed events - Implement idempotent webhook handling using
event_id - Query API for current status if webhooks seem out of order
Support
For status-related issues:
- Check Error Codes Reference for error details
- Review Webhook Event Types for event flow
- Contact support with:
- Transaction reference
- Current status
- Expected status
- Timestamp of issue
Related Documentation
- Error Codes Reference - Error code definitions
- Webhook Event Types - Webhook events by status
- Transaction Flow Guide - Complete transaction lifecycle
- Idempotency Guide - Handling duplicate requests
- Rate Limiting - Avoiding rate limits when polling status