Troubleshooting
Common errors and integration issues — their causes and how to solve them quickly.
When something goes wrong, start here. This page covers the most frequent issues developers encounter with Wajub, organized by category.
Authentication errors
401 Unauthorized — "Invalid API credentials"
Cause: Your API key is missing, expired, revoked, or you're using a sandbox key in production (or vice versa).
Solution:
- Verify you're using the correct key for the environment (test:
pk_test.…/sk_test.…, live:pk.…/sk.…). - Check that the key hasn't been revoked in Dashboard → Developers → API Keys.
- Ensure the key is passed in the
Authorizationheader, without theBearerprefix.
403 Forbidden — "Private key used from browser"
Cause: A private key (sk.…) was detected in a browser request. Wajub blocks this
automatically as a security measure.
Solution:
- Move the private key call to your backend.
- Use a public key (
pk.…) for client-side payment initialization. - If this was accidental, check for a private key accidentally bundled into frontend code.
See Authentication for full details on the three-key model.
406 Not Acceptable — "Private key required"
Cause: You're trying to perform a sensitive operation (refund, transfer, balance read) with a public key.
Solution: Use a private key (sk.…) for these operations. Public keys are only for
payment initialization and reading public resources.
Payment errors
Payment stays in "pending"
Cause: The customer hasn't completed the payment on the hosted page (pay.wajub.com),
or the USSD prompt hasn't been answered.
Solution:
- Verify the customer received the USSD / push notification.
- Check that the
authorization_urlyou sent them is correct. - The payment will expire after 30 minutes — reinitialize if the window has passed.
Payment fails with "insufficient_funds"
Cause: The customer's Mobile Money wallet or card doesn't have enough funds.
Solution: Display a clear error to the customer asking them to top up and try again.
The reason field on the transaction object gives the exact cause — see
Failure Reasons for the complete catalog.
Payment succeeds but timeout occurs
Cause: The operator confirmed the payment but the response was delayed, causing a perceived timeout.
Solution: Never assume failure — always verify the transaction status server-side
with GET /payments/{id} or wait for the payment.succeeded webhook before acting.
Webhook errors
Webhooks are not being received
Cause: The endpoint URL is unreachable, the signature isn't being verified, or the webhook endpoint isn't configured.
Solution:
- Check that the endpoint URL is publicly accessible from the internet.
- Verify you've configured the endpoint in Dashboard → Developers → Webhooks.
- Use the CLI listen command to test webhooks locally.
- Check Konsole → Webhook Tester for delivery logs.
Receiving duplicate webhooks
Cause: Wajub retries webhooks that don't receive a 200 response within the timeout
window.
Solution: Make your webhook handler idempotent by deduplicating on event.id. Also
ensure your endpoint responds with 200 before processing the event body — any
processing delay can trigger a retry.
Rate limiting
429 Too Many Requests
Cause: You've exceeded your plan's rate limit for the current 60-second window.
Solution:
- Check the
X-RateLimit-*response headers to see your current quota. - Implement exponential back-off in your retry logic.
- For batch jobs, add delays between requests and respect the
Retry-Afterheader. - Contact support to increase your quota if needed.
See Rate Limits for quotas per plan and retry strategies.
Idempotency
422 — "Idempotency key conflict"
Cause: You reused an Idempotency-Key with a different request body on POST /payments.
Solution: Each unique business intent should have its own idempotency key. Generate a new UUID v4 for each distinct payment intent. If you legitimately want to replay the same request, use the exact same body.
Still stuck?
Debug with Konsole
Open Konsole to inspect every request/response pair, replay webhooks, and trace the exact routing path for each transaction. It's the fastest way to understand what's happening.
Was this page helpful?