Skip to content

Troubleshooting ​

My webhook does not arrive ​

Diagnosis ​

  1. Dashboard → Webhooks → Logs: is the request listed?
    • No → the event was not triggered. Check that the transaction reached success/failed/cancelled/expired. Force a pull with POST /v1/payments/{id}/verify.
    • Yes, with a non-2xx http_status → your server refused it. See “My endpoint returns 4xx/5xx”.
    • Yes, with error_message: "timeout" → your server took longer than 15 seconds. See “Timeout”.

My endpoint returns 4xx/5xx ​

http_statusTypical cause
400Body parsing: your server reads the payload before verifying the signature
401 / 403An auth middleware blocks external requests — exempt /webhooks/soamtar
404Wrong route — check the exact URL in the dashboard
422Framework validation — accept the payload as is
500Application error — check your logs
502 / 503Reverse proxy down or app not started

Fix: exclude your webhook route from CSRF protection (e.g. withoutMiddleware([VerifyCsrfToken::class]) in Laravel) and from your auth middleware.

Timeout ​

Your handler must answer 2xx in less than 15 seconds. For long processing:

php
// Bad
public function webhook(Request $request) {
    $this->processOrder($request);     // 30 seconds
    $this->sendEmail($request);
    return response('ok');
}

// Good
public function webhook(Request $request) {
    ProcessSoamtarWebhookJob::dispatch($request->all());
    return response('ok');             // immediate
}

My server receives the webhook 5 times ​

That is a retry, which is expected. Causes:

  1. You answer 2xx but after more than 15 s → SOAMTAR already recorded a timeout
  2. You answer non-2xx
  3. You answer 2xx but SOAMTAR never receives the response (network)

Fix: de-duplicate on your side on the pair (event, data.id).

sql
CREATE UNIQUE INDEX uq_inbound_event ON your_webhook_log(event, transaction_id);

Invalid webhook signature ​

Symptom ​

Your HMAC check fails although you use the right secret.

Cause 1: body modified before verification ​

Express, Symfony and Laravel parse JSON automatically. You must verify the signature on the raw body, before any parsing.

js
// Bad
app.use(express.json())   // parses JSON
app.post('/webhook', (req, res) => {
  verify(req.body)        // req.body is already an object, not the raw body
})

// Good
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  verify(req.body.toString())   // raw
  const event = JSON.parse(req.body.toString())
})

Cause 2: HMAC encoding ​

Compare lowercase hex on both sides (PHP hash_hmac returns lowercase; check Node). Use hash_equals / crypto.timingSafeEqual, not ===.

Cause 3: expired timestamp ​

|now - t| > 300s is rejected. Check your server clock (NTP). If you are replaying old webhooks by hand, raise the tolerance or use the verify pull instead.

Idempotency-Key conflict ​

Symptom ​

409 idempotency_conflict when you resend the request.

Diagnosis ​

bash
curl -X POST .../v1/payments \
  -H "Idempotency-Key: abc-123" \
  -d '{"merchant_ref":"ORDER-7","amount":"150.00","currency":"SLE"}'
# 201

curl -X POST .../v1/payments \
  -H "Idempotency-Key: abc-123" \
  -d '{"merchant_ref":"ORDER-7","amount":"150.00","currency":"SLE","description":"oops"}'
# 409 — different body

Fix:

  • To retry: same key, exactly the same body.
  • For a new request: a new key.

Best practice ​

Generate the key when the customer taps "Pay" and persist it (local storage / database) until the final outcome. Do not regenerate it on each HTTP retry.

Transaction stuck in pending ​

Diagnosis ​

bash
curl -X POST https://app.so-amtar.com/v1/payments/{id}/verify \
  -H "Authorization: Bearer sk_test_xxx"

The verify pull queries Orange Money and synchronises the status. If Orange Money confirms, the transaction moves on and a webhook is sent to you.

If nothing changes ​

  • The customer has not confirmed the Orange Money prompt with their PIN (the prompt stays open for about 5 minutes): ask them to check their phone, or start a new payment.
  • Orange Money is having an incident: check with support.
  • The transaction expires automatically at expires_at (2 hours by default).
  • Still stuck: email support@so-amtar.com with the SOAMTAR reference (TXN-...).

Refunding an Orange Money payment ​

Use POST /v1/refunds with the payment's transaction_id (see Refunds). If the call fails:

Error codeCauseFix
scope_insufficient (403)The API key lacks can_refundEnable the scope on the key in the dashboard
refunds_disabledRefunds are not enabled for your accountContact support
transaction_not_refundableThe payment is not success (or already refunded)Only refund successful payments
refund_environment_mismatchTest key on a live payment, or the reverseUse the key of the payment's environment
refund_payer_phone_missingThe payment has no payer phone to creditRefund the customer outside SOAMTAR
refund_exceeds_refundable / transaction_already_fully_refundedAmount above what remains refundableLower amount or stop

A refund in processing is waiting for Orange Money to confirm the credit; the final status arrives as refund.success or refund.failed (check failure_reason). You can also poll GET /v1/refunds/{id}.

The displayed balance does not match my settlements ​

ComponentIncluded in the displayed balance?
success not yet settledYes
pendingNo
success already settledNo
Refund or payout issuedYes (deducted)
Open chargebackReserved on a separate balance

The "available" balance differs from the "ledger" balance; the dashboard shows both.

A live key cannot find my object ​

You are using a live key on an object created in the sandbox (or the reverse). Use the key of the matching mode: ids are strictly scoped by mode.

API connection problems (SSL, timeout…) ​

SSL error: SSL certificate problem: unable to get local issuer certificate ​

Usually on Windows servers or old PHP/cURL installs without an up-to-date CA bundle.

Fix:

  1. Download the official cacert.pem from curl.se/ca/cacert.pem.
  2. Point php.ini to it:
    ini
    curl.cainfo = "C:/path/to/cacert.pem"
    openssl.cafile = "C:/path/to/cacert.pem"
  3. Restart your web server.

Error: Connection Timeout ​

If your server cannot reach app.so-amtar.com:

  1. Firewall: your server must be allowed outbound requests on port 443 (HTTPS).
  2. DNS: check that app.so-amtar.com resolves from your server.
  3. Proxy: if your server uses a proxy, configure the SDK accordingly (or the HTTP_PROXY environment variables).

Error: 401 Unauthorized (invalid key) ​

  1. Use the secret key (sk_...) for backend calls, not the public key (pk_...).
  2. Check the mode: an sk_test_ key only works for sandbox objects.
  3. Make sure no space or line break was copied with the key.

Going further ​

  • Support: support@so-amtar.com

SOAMTAR Payment API Documentation.