Webhooks / Callbacks
Receive asynchronous updates when your order completes.
Instead of polling the order-status endpoint, we strongly recommend providing a Callback URL in your Reseller Settings.
When an order reaches a terminal state (success or failed), our background worker will send an HTTP POST request to your callback URL.
Payload Format
We will send a JSON payload to your server:
{
"ref_id": "R-12345",
"order_code": "AGN-D7E8F9A0B1C2",
"status": "success",
"product_code": "MLBBPH-86",
"product_name": "Mobile Legends 86 Diamonds",
"price": 25000,
"serial_number": "ALG-A1B2C3D4EF",
"message": "Order completed successfully",
"failure_code": null,
"failure_reason": null
}Failed Order Payload
A failed order now carries the reason. status and message are unchanged; failure_code and failure_reason were added beside them, so existing integrations keep working untouched.
{
"ref_id": "R-12346",
"order_code": "AGN-B4C5D6E7F8A9",
"status": "failed",
"product_code": "MLBBPH-86",
"product_name": "Mobile Legends 86 Diamonds",
"price": 25000,
"serial_number": null,
"message": "Order failed",
"failure_code": "PRODUCT_UNAVAILABLE",
"failure_reason": "The order was rejected: product not found"
}Both fields can be null on a failure
Some upstream failures reach us as a bare “failed” with no explanation. We send null rather than inventing a reason. failure_code is an open set; handle the codes you know and display failure_reason for the rest. The full list is on the Order Status page.
order_code: new, additive
order_code is a short, human-quotable rendering of our internal order id (AGN- + 12
characters) that your support team can read out loud. It was ADDED beside the existing keys;
ref_id, status, product_code, product_name, price, serial_number and message all
keep their exact names, types and values. Match callbacks on ref_id as you always have; never
treat order_code as an idempotency key.
Signature note
Every new field is inside the signed JSON body, so your existing HMAC verification covers them with no change. Just make sure your parser does not reject unknown keys.
Serial Number Format
The serial_number is always prefixed with ALG-. For voucher products the real redeemable code is appended after a /:
{
"ref_id": "R-99001",
"order_code": "AGN-F1A2B3C4D5E6",
"status": "success",
"product_code": "ALGAN-STEAMPHP-500",
"product_name": "Steam Voucher PHP 500",
"price": 135000,
"serial_number": "ALG-A1B2C3D4EF/STEAMPH-9XQ2-K7MP-44TZ",
"message": "Order completed successfully",
"failure_code": null,
"failure_reason": null
}Delivering voucher codes
Split serial_number on the last /: serialNumber.slice(serialNumber.lastIndexOf('/') + 1). Everything after the last / is the actual code your customer redeems (e.g. STEAMPH-9XQ2-K7MP-44TZ). A voucher serial can be three segments deep (INV-XXXXXXXXXX/ALG-ref/CODE), so splitting on the first / would hand your customer our internal reference glued to their code. For instant/direct products there is no /; the value is a branded reference token only, since the top-up is delivered straight to the target account.
Security
To ensure the webhook is genuinely from Algan, we include an X-Signature and X-Timestamp header in the request. The signature is generated using the exact same HMAC-SHA256 logic used for your requests. Make sure your code is robust enough to accept either signature or body signed by Algan, or accept both. Also, whitelist our IP Address too at: 208.77.246.15
Verification Steps
- Verify the timestamp is within 5 minutes of your server’s current time.
- Hash the received raw JSON payload and the timestamp using your
API Secret. - Compare your generated hash with the
X-Signatureheader. If they match, the request is authentic.
Retry Policy
If your server does not answer with a 2xx HTTP status code, or times out after 5 seconds, we retry the callback automatically. The times below are measured from the first attempt, because the waits run back to back:
Retry Schedule
| Parameter | Required | Description |
|---|---|---|
Attempt 1 | No | Immediate: sent right after the order reaches a terminal state (T+0s). |
Attempt 2 | No | 5 seconds after the first attempt (T+5s). |
Attempt 3 | No | 15 seconds after the first attempt (T+15s), a 10 second wait after attempt 2. |
Attempt 4 (Final) | No | 30 seconds after the first attempt (T+30s), a 15 second wait after attempt 3. |
Sizing your reconciliation window
Worst case, the last attempt is fired at T+30s and can still be in flight for its own 5 second timeout, so allow at least 35 seconds from the terminal state before you treat a callback as missing. If your endpoint is slow to answer, every attempt shifts later by however long it holds the connection open.
Permanently Failed
If all 4 attempts fail, the callback is marked as permanently failed. Our worker will not retry again automatically.
You have two options:
- Resend from Dashboard: Go to your Reseller Dashboard → Orders → click the failed order → “Resend Callback”.
- Poll manually: Use the
GET /api/v1/order-status?ref_id=...endpoint to fetch the current order status.