Skip to content

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:

  1. Webhooks (push, primary): an event is sent as soon as a status changes
  2. Verify endpoint (pull, on demand): you call POST /v1/payments/{id}/verify
  3. 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.

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 ​

CaseCauseFix
Webhook received but not processed by your serverApplication bugReplay it with "Resend" in the dashboard
pending on your side, success at SOAMTARLost webhookPOST /verify, then sync
success on your side, failed at SOAMTARRare race conditionManual audit and correction
Refund at SOAMTAR but not on your sideMissed refund webhookGET /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"
done

verify 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:

SourceField
Your ordersSUM(amount) WHERE payment_status = 'success'
SOAMTAR dashboardMonthly volume (livemode = true)
Settlements receivedSUM(net_amount) paid into your bank account
Refunds issuedSUM(amount) WHERE refund.status = 'success'

Expected equality: Volume - Refunds - Fees - Lost disputes = Settlements. All amounts are in leones (SLE).

SOAMTAR Payment API Documentation.