Reconciliation
How to make sure your database and SOAMTAR always stay in sync.
Three lines of defence
SOAMTAR uses three complementary mechanisms to stay consistent:
- Webhooks (push, primary): an event is sent as soon as a status changes
- Verify endpoint (pull, on demand): you call
POST /v1/payments/{id}/verify - Internal reconciliation job (pull, automatic): every 5 minutes SOAMTAR sweeps stuck transactions, re-checks them with Orange Money and sends any missed webhooks
As a merchant, you run the fourth line: your own daily cron.
Recommended daily cron
php
// Schedule daily at 03:00 (Freetown time, GMT)
class ReconcileSoamtarTransactions extends Command
{
public function handle()
{
// Every transaction still pending in your database for more than 30 min
$stuck = Order::where('payment_status', 'pending')
->where('created_at', '<', now()->subMinutes(30))
->whereNotNull('soamtar_id')
->get();
foreach ($stuck as $order) {
$response = Http::withToken(env('SOAMTAR_SECRET_KEY'))
->post("https://app.so-amtar.com/v1/payments/{$order->soamtar_id}/verify");
$status = $response->json('data.status');
// Sync your database
$order->update(['payment_status' => $status]);
// On success, trigger your business workflows (delivery, etc.)
if ($status === 'success' && $order->wasChanged('payment_status')) {
FulfillOrder::dispatch($order);
}
}
}
}Weekly audit
Once a week, compare volumes:
sql
-- Your database
SELECT DATE(paid_at) AS day, COUNT(*) AS count, SUM(amount) AS total_sle
FROM orders
WHERE payment_status = 'success' AND paid_at >= NOW() - INTERVAL '7 days'
GROUP BY DATE(paid_at);bash
# SOAMTAR
curl 'https://app.so-amtar.com/v1/payments?status=success&created_from=2026-04-07&per_page=100' \
-H "Authorization: Bearer sk_live_xxx"Any gap above 0.01% needs investigation. Contact support@so-amtar.com with:
- Start and end of the time window
- Expected vs. observed volume (in SLE)
- The list of mismatching
merchant_ref
Typical mismatches
| Case | Cause | Fix |
|---|---|---|
| Webhook received but not processed by your server | Application bug | Replay it with "Resend" in the dashboard |
pending on your side, success at SOAMTAR | Lost webhook | POST /verify, then sync |
success on your side, failed at SOAMTAR | Rare race condition | Manual audit and correction |
| Refund at SOAMTAR but not on your side | Missed refund webhook | GET /v1/refunds?transaction_id=... |
Missed webhooks: automatic recovery
If you know you lost webhooks (internal incident, failed deployment), force a replay:
bash
# For every transaction of the day
for ID in $(jq -r '.data[].id' < my-orders-today.json); do
curl -X POST "https://app.so-amtar.com/v1/payments/$ID/verify" \
-H "Authorization: Bearer sk_live_xxx"
doneverify has no effect if the transaction already has the right status at SOAMTAR (idempotent). If it detects a new transition, it sends the webhook to your endpoint again.
Monthly accounting reconciliation
Compare:
| Source | Field |
|---|---|
| Your orders | SUM(amount) WHERE payment_status = 'success' |
| SOAMTAR dashboard | Monthly volume (livemode = true) |
| Settlements received | SUM(net_amount) paid into your bank account |
| Refunds issued | SUM(amount) WHERE refund.status = 'success' |
Expected equality: Volume - Refunds - Fees - Lost disputes = Settlements. All amounts are in leones (SLE).