Skip to content

Solución de problemas ​

Mi webhook no llega ​

Diagnóstico ​

  1. Panel → Webhooks → Registros: ¿aparece la solicitud?
    • No → el evento no se generó. Compruebe que la transacción llegó a success/failed/cancelled/expired. Fuerce una consulta con POST/v1/payments/{id}/verify.
    • Sí, con un http_status distinto de 2xx → su servidor la rechazó. Vea «Mi endpoint responde 4xx/5xx».
    • Sí, con error_message: "timeout" → su servidor tardó más de 15 segundos. Vea «Tiempo de espera agotado».

Mi endpoint responde 4xx/5xx ​

http_statusCausa habitual
400Lectura del cuerpo: su servidor lee el payload antes de verificar la firma
401 / 403Un middleware de autenticación bloquea las solicitudes externas: excluya /webhooks/soamtar
404Ruta incorrecta: compruebe la URL exacta en el panel
422Validación del framework: acepte el payload tal cual
500Error de la aplicación: consulte sus registros
502 / 503Proxy inverso caído o aplicación no iniciada

Solución: excluya su ruta de webhook de la protección CSRF (p. ej. withoutMiddleware([VerifyCsrfToken::class]) en Laravel) y de su middleware de autenticación.

Tiempo de espera agotado ​

Su handler debe responder 2xx en menos de 15 segundos. Para un procesamiento largo:

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
}

Mi servidor recibe el webhook 5 veces ​

Es un reintento, algo esperado. Causas:

  1. Responde 2xx pero tras más de 15 s → SOAMTAR ya registró un tiempo agotado
  2. Responde con un código distinto de 2xx
  3. Responde 2xx pero SOAMTAR nunca recibe la respuesta (red)

Solución: elimine duplicados en su lado según el par (event, data.id).

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

Firma de webhook no válida ​

Síntoma ​

Su verificación HMAC falla aunque usa el secreto correcto.

Causa 1: cuerpo modificado antes de la verificación ​

Express, Symfony y Laravel interpretan el JSON automáticamente. Debe verificar la firma sobre el cuerpo en bruto, antes de cualquier interpretación.

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())
})

Causa 2: codificación del HMAC ​

Compare hexadecimal en minúsculas en ambos lados (hash_hmac de PHP devuelve minúsculas; compruébelo en Node). Use hash_equals / crypto.timingSafeEqual, no ===.

Causa 3: marca de tiempo caducada ​

|now - t| > 300s se rechaza. Compruebe el reloj de su servidor (NTP). No amplíe la tolerancia para procesar entregas antiguas: reenvíelas desde Dashboard → Webhook logs → Resend (cada intento se vuelve a firmar con una marca de tiempo nueva) o use la consulta verify.

Conflicto de Idempotency-Key ​

Síntoma ​

409 idempotency_conflict al reenviar la solicitud.

Diagnóstico ​

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

Solución:

  • Para reintentar: misma clave y exactamente el mismo cuerpo.
  • Para una nueva solicitud: una nueva clave.

Buena práctica ​

Genere la clave cuando el cliente pulsa «Pagar» y consérvela (almacenamiento local o base de datos) hasta el resultado final. No la regenere en cada reintento HTTP.

Transacción bloqueada en pending ​

Diagnóstico ​

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

La consulta verify pregunta a Orange Money y sincroniza el estado. Si Orange Money confirma, la transacción avanza y se le envía un webhook.

Si nada cambia ​

  • El cliente no ha confirmado la solicitud de Orange Money con su PIN (la solicitud queda abierta unos 5 minutos): pídale que revise su teléfono o inicie un nuevo pago.
  • Orange Money tiene un incidente: consulte con el soporte.
  • La transacción caduca automáticamente en expires_at (2 horas por defecto).
  • Si sigue bloqueada: escriba a support@so-amtar.com con la referencia SOAMTAR (TXN-...).

Reembolsar un pago Orange Money ​

Use POST/v1/refunds (clave con el scope can_refund):

  1. Envíe el transaction_id del pago; omita amount para reembolsar el importe restante o indíquelo para un reembolso parcial.
  2. El importe se abona al número Orange Money del pagador y se descuenta de su saldo. El reembolso queda en processing hasta la confirmación de Orange; el resultado llega por refund.success / refund.failed.
  3. Si recibe refund_payer_phone_missing, el pago no tiene número de pagador: contacte con el soporte.

Ver Reembolsos (en inglés).

El saldo mostrado no coincide con mis liquidaciones ​

Componente¿Incluido en el saldo mostrado?
success aún no liquidadoSí
pendingNo
success ya liquidadoNo
Reembolso o pago saliente emitidoSí (deducido)
Contracargo abiertoReservado en un saldo aparte

El saldo «disponible» es distinto del saldo «contable»; el panel muestra ambos.

Una clave live no encuentra mi objeto ​

Está usando una clave live sobre un objeto creado en sandbox (o al revés). Use la clave del modo correspondiente: los identificadores dependen estrictamente del modo.

Problemas de conexión a la API (SSL, tiempo de espera…) ​

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

Suele ocurrir en servidores Windows o en instalaciones antiguas de PHP/cURL sin un paquete de certificados CA actualizado.

Solución:

  1. Descargue el cacert.pem oficial desde curl.se/ca/cacert.pem.
  2. Apunte php.ini hacia él:
    ini
    curl.cainfo = "C:/chemin/vers/votre/cacert.pem"
    openssl.cafile = "C:/chemin/vers/votre/cacert.pem"
  3. Reinicie su servidor web.

Error: Connection Timeout ​

Si su servidor no puede llegar a app.so-amtar.com:

  1. Cortafuegos: su servidor debe poder hacer solicitudes salientes en el puerto 443 (HTTPS).
  2. DNS: compruebe que app.so-amtar.com se resuelve desde su servidor.
  3. Proxy: si su servidor usa un proxy, configure el SDK en consecuencia (o las variables de entorno HTTP_PROXY).

Error: 401 Unauthorized (clave no válida) ​

  1. Use la clave secreta (sk_...) para las llamadas desde el backend, no la clave pública (pk_...).
  2. Compruebe el modo: una clave sk_test_ solo sirve para los objetos de sandbox.
  3. Asegúrese de no haber copiado espacios ni saltos de línea con la clave.

Para saber más ​

  • Soporte: support@so-amtar.com

SOAMTAR Payment API Documentation.