Managing Transactions & Withdrawals via the DGateway API
DGateway is a unified payment and commerce platform for Africa. If you're building a dashboard that shows your users their transaction history, withdrawal status, or lets them request payouts — this guide covers every API endpoint you need.
Authentication
All endpoints require your API key in the X-API-Key header:
curl -H "X-API-Key: dgw_live_your_key_here" \
https://dgatewayapi.desispay.com/v1/payments/transactions1. Listing Transactions
Fetch All Transactions
GET /v1/payments/transactions?page=1&page_size=20&mode=live
Query Parameters:
| Param | Type | Description |
|---|---|---|
page | int | Page number (default: 1) |
page_size | int | Items per page (default: 20, max: 100) |
mode | string | live or test (default: all) |
status | string | Filter by: pending, completed, failed |
direction | string | collect or disburse |
phone | string | Search by phone number |
Response:
{
"data": [
{
"id": 142,
"reference": "dgw_abc123def456",
"provider_slug": "iotec",
"provider_ref": "39463627506",
"direction": "collect",
"amount": 50000,
"platform_fee": 4000,
"net_amount": 46000,
"currency": "UGX",
"status": "completed",
"phone_number": "256770123456",
"description": "Payment for order #1234",
"failure_reason": "",
"is_test": false,
"created_at": "2026-04-09T14:03:00Z"
}
],
"meta": {
"total": 118,
"page": 1,
"page_size": 20,
"pages": 6
}
}Get Single Transaction
GET /v1/payments/transactions/:reference
Returns the full transaction object by its reference (e.g., dgw_abc123def456).
2. Checking Transaction Status
Verify (Webhook Alternative)
POST /v1/webhooks/verify
Content-Type: application/json
{
"reference": "dgw_abc123def456"
}
Response:
{
"data": {
"reference": "dgw_abc123def456",
"status": "completed",
"amount": 50000,
"currency": "UGX"
}
}When to use: Call this endpoint when polling for payment status (every 5 seconds) or when you suspect a webhook was missed.
Active Provider Check
The verify endpoint actively queries the payment provider (Iotec/Relworx) if the transaction is still pending. This means even if the webhook hasn't arrived, you'll get the real status from the provider.
3. Requesting a Withdrawal
Create Withdrawal
POST /v1/payments/disburse
Content-Type: application/json
{
"amount": 50000,
"currency": "UGX",
"phone_number": "256770123456",
"provider": "iotec",
"description": "Payout to vendor"
}
Response:
{
"data": {
"reference": "dgw_wd_xyz789",
"provider_ref": "39448500011",
"status": "pending",
"amount": 50000,
"currency": "UGX"
},
"message": "Disbursement initiated"
}Important notes:
- The amount is deducted from your app's available balance (collected net minus already withdrawn)
- Minimum withdrawal: 500 UGX
- The phone number must be in format
256XXXXXXXXXor0XXXXXXXXX - Provider must be
iotecorrelworx
Check Withdrawal Status
Use the same verify endpoint:
POST /v1/webhooks/verify
{
"reference": "dgw_wd_xyz789"
}
4. Listing Withdrawals
Fetch Withdrawal History
GET /v1/payments/withdrawals?page=1&page_size=20
Response:
{
"data": [
{
"id": 312,
"reference": "dgw_wd_xyz789",
"amount": 60000,
"currency": "UGX",
"method": "mobile_money",
"destination": "+256744574953",
"destination_name": "JOHN DOE",
"provider_slug": "iotec",
"status": "completed",
"created_at": "2026-04-15T02:19:00Z"
}
]
}5. Building a Transaction Dashboard
Here's a complete Next.js implementation for displaying transactions with status check buttons:
API Routes
// app/api/transactions/route.ts
import { NextResponse } from "next/server";
const API_URL = process.env.DGATEWAY_API_URL!;
const API_KEY = process.env.DGATEWAY_API_KEY!;
export async function GET(req: Request) {
const { searchParams } = new URL(req.url);
const page = searchParams.get("page") || "1";
const mode = searchParams.get("mode") || "live";
const res = await fetch(
`${API_URL}/v1/payments/transactions?page=${page}&page_size=20&mode=${mode}`,
{ headers: { "X-API-Key": API_KEY } },
);
return NextResponse.json(await res.json());
}// app/api/transactions/[ref]/check/route.ts
import { NextResponse } from "next/server";
export async function POST(
req: Request,
{ params }: { params: { ref: string } },
) {
const res = await fetch(
`${process.env.DGATEWAY_API_URL}/v1/webhooks/verify`,
{
method: "POST",
headers: {
"X-API-Key": process.env.DGATEWAY_API_KEY!,
"Content-Type": "application/json",
},
body: JSON.stringify({ reference: params.ref }),
},
);
return NextResponse.json(await res.json());
}Transaction Table Component
"use client";
import { useState } from "react";
interface Transaction {
reference: string;
amount: number;
currency: string;
status: string;
phone_number: string;
direction: string;
created_at: string;
failure_reason?: string;
}
export function TransactionTable({
transactions,
}: {
transactions: Transaction[];
}) {
const [checking, setChecking] = useState<string | null>(null);
const [statuses, setStatuses] = useState<Record<string, string>>({});
const checkStatus = async (ref: string) => {
setChecking(ref);
try {
const res = await fetch(`/api/transactions/${ref}/check`, {
method: "POST",
});
const data = await res.json();
if (data.data?.status) {
setStatuses((prev) => ({ ...prev, [ref]: data.data.status }));
}
} catch {
alert("Check failed");
}
setChecking(null);
};
return (
<table className="w-full text-sm">
<thead>
<tr className="border-b text-left text-gray-500">
<th className="py-3 px-4">Reference</th>
<th className="py-3 px-4">Amount</th>
<th className="py-3 px-4">Status</th>
<th className="py-3 px-4">Phone</th>
<th className="py-3 px-4">Date</th>
<th className="py-3 px-4">Actions</th>
</tr>
</thead>
<tbody>
{transactions.map((tx) => {
const currentStatus = statuses[tx.reference] || tx.status;
return (
<tr key={tx.reference} className="border-b hover:bg-gray-50">
<td className="py-3 px-4 font-mono text-xs">{tx.reference}</td>
<td className="py-3 px-4 font-semibold">
{tx.currency} {tx.amount.toLocaleString()}
</td>
<td className="py-3 px-4">
<span
className={`px-2 py-1 rounded-full text-xs font-medium ${
currentStatus === "completed"
? "bg-green-100 text-green-700"
: currentStatus === "failed"
? "bg-red-100 text-red-700"
: "bg-yellow-100 text-yellow-700"
}`}
>
{currentStatus}
</span>
</td>
<td className="py-3 px-4 text-gray-500">{tx.phone_number}</td>
<td className="py-3 px-4 text-gray-500">
{new Date(tx.created_at).toLocaleString()}
</td>
<td className="py-3 px-4">
{currentStatus === "pending" && (
<button
onClick={() => checkStatus(tx.reference)}
disabled={checking === tx.reference}
className="px-3 py-1 bg-blue-100 text-blue-700 rounded text-xs font-medium hover:bg-blue-200 disabled:opacity-50"
>
{checking === tx.reference
? "Checking..."
: "⟳ Check Status"}
</button>
)}
{tx.failure_reason && (
<span className="text-xs text-red-500 block mt-1">
{tx.failure_reason}
</span>
)}
</td>
</tr>
);
})}
</tbody>
</table>
);
}6. Building a Withdrawal Dashboard
"use client";
import { useState } from "react";
export function WithdrawalForm() {
const [amount, setAmount] = useState("");
const [phone, setPhone] = useState("");
const [status, setStatus] = useState<
"idle" | "loading" | "success" | "error"
>("idle");
const [result, setResult] = useState<any>(null);
const handleWithdraw = async () => {
setStatus("loading");
try {
const res = await fetch("/api/withdraw", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
amount: Number(amount),
currency: "UGX",
phone_number: phone,
provider: "iotec",
}),
});
const data = await res.json();
if (!res.ok) throw new Error(data.error?.message || "Withdrawal failed");
setResult(data.data);
setStatus("success");
} catch (err: any) {
setResult({ error: err.message });
setStatus("error");
}
};
return (
<div className="max-w-md p-6 border rounded-xl">
<h2 className="text-lg font-bold mb-4">Request Withdrawal</h2>
<div className="space-y-3 mb-4">
<input
type="number"
min="500"
value={amount}
onChange={(e) => setAmount(e.target.value)}
placeholder="Amount (min 500 UGX)"
className="w-full border rounded-lg px-3 py-2"
/>
<input
type="tel"
value={phone}
onChange={(e) => setPhone(e.target.value)}
placeholder="256700000000"
className="w-full border rounded-lg px-3 py-2"
/>
</div>
<button
onClick={handleWithdraw}
disabled={!amount || !phone || status === "loading"}
className="w-full bg-green-600 text-white py-3 rounded-lg font-semibold disabled:opacity-50"
>
{status === "loading"
? "Processing..."
: `Withdraw UGX ${Number(amount || 0).toLocaleString()}`}
</button>
{status === "success" && (
<div className="mt-4 p-3 bg-green-50 rounded-lg">
<p className="text-green-700 font-medium">Withdrawal initiated!</p>
<p className="text-sm text-green-600">
Reference: {result?.reference}
</p>
</div>
)}
{status === "error" && (
<div className="mt-4 p-3 bg-red-50 rounded-lg">
<p className="text-red-700">{result?.error}</p>
</div>
)}
</div>
);
}7. Common Issues & Solutions
"Withdrawal shows pending for a long time"
Cause: The provider hasn't confirmed the disbursement yet.
Fix:
- Call the verify endpoint to actively check with the provider
- If still pending after 5 minutes, the provider may be experiencing delays
- Check System Health in the dashboard for line status
"Transaction completed on provider but shows pending in DGateway"
Cause: The webhook from the provider didn't arrive.
Fix:
- Call
POST /v1/webhooks/verifywith the reference — this actively checks with the provider - If the provider confirms completed, DGateway updates the status automatically
- For admin: use the recheck button (⟳) on the admin transactions page
"Insufficient balance for withdrawal"
Cause: Your available balance (collected net minus already withdrawn) is less than the requested amount.
Fix:
- Check your balance via
GET /v1/wallets - Available = total collected (net) - total withdrawn
- Pending withdrawals also count against your available balance
"Invalid phone number"
Fix: Phone numbers must be:
256XXXXXXXXX(12 digits, starting with country code)0XXXXXXXXX(10 digits, starting with 0)- No spaces, dashes, or
+prefix - Must be a valid MTN or Airtel Uganda number
API Reference Summary
| Endpoint | Method | Description |
|---|---|---|
/v1/payments/collect | POST | Collect payment from customer |
/v1/payments/disburse | POST | Send money to phone number |
/v1/payments/transactions | GET | List all transactions (paginated) |
/v1/payments/transactions/:ref | GET | Get single transaction |
/v1/payments/withdrawals | GET | List all withdrawals |
/v1/webhooks/verify | POST | Verify/check transaction status |
/v1/wallets | GET | Get balance per provider |
Need help? Join our WhatsApp Support Group or check the full docs.