How to know when an asynchronous top-up finished

Mauro Chojrin

Mauro Chojrin

3 min

When using Reloadly’s AirTime API there are two ways of making a top-up:

  1. Synchronous
  2. Asynchronous

The difference between a synchronous and an asynchronous operation lies in the fact that, when making a syncrhonous call the client won’t get a response until de server has finished processing the request, whereas in the asynchronous one the response is immediate, though the operation might not have even started yet.

In the case of an asynchronous operation, the moment to spawn the actions that must take place after the top-up has taken effect is not easy to determine.

The Reloadly API offers two ways for you to know when the time has come.

Via long-poll

One way to go about it is to have a process regularly asking Reloadly for the status of the operation.

To do this you’ll rely on this endpoint (https://docs.reloadly.com/airtime/top-ups/top-up-status).

Here’s how it’d go:

  1. Get the access token
  2. Make the request for an async top-up
  3. Make a request to get the status of the request. If the result obtained is:
    • PROCESSING wait a little while and try again
    • SUCCESSFUL or REFUNDED execute appropriate followup actions

This how you could do this through the CLI, assuming you’ve already got your access token:

curl --location --request POST https://topups.reloadly.com/topups-async --header 'Authorization: Bearer YOUR_TOKEN' --header 'Accept: application/com.reloadly.topups-v1+json' --header 'Content-Type: application/json' --data-raw '{
		"operatorId":"YOUR_OPERATOR_ID",
		"amount":"10",
		"recipientPhone": {
			"countryCode": "YOUR_ISO_COUNTRY_CODE",
			"number": "YOUR_LOCAL_PHONE_NUMBER"
		}
	}

This request would result in something like:

{
  "transactionId": 28805
}

Once you have the transactionId you can followup to get a status update like this:

curl --location --request GET https://topups.reloadly.com/topups/28805/status --header 'Accept: application/com.reloadly.topups-v1+json' --header 'Authorization: Bearer YOUR_TOKEN'

Which will return something like:

{
  "code": null,
  "message": null,
  "status": "SUCCESSFUL",
  "transaction": {
    "transactionId": 28805,
    "status": "SUCCESSFUL",
    "operatorTransactionId": null,
    "customIdentifier": null,
    "recipientPhone": "YOUR_LOCAL_PHONE_NUMBER",
    "recipientEmail": null,
    "senderPhone": null,
    "countryCode": "YOUR_COUNTRY_ISO_CODE",
    "operatorId": YOUR_OPERATOR_ID,
    "operatorName": "YOUR_OPERATOR_NAME",
    "discount": 0,
    "discountCurrencyCode": "EUR",
    "requestedAmount": 10,
    "requestedAmountCurrencyCode": "EUR",
    "deliveredAmount": 10,
    "deliveredAmountCurrencyCode": "EUR",
    "transactionDate": "2022-03-18 09:11:24",
    "pinDetail": null,
    "balanceInfo": {
      "oldBalance": 741.01448,
      "newBalance": 731.01448,
      "cost": 10,
      "currencyCode": "EUR",
      "currencyName": "Euro",
      "updatedAt": "2022-03-18 13:11:24"
    }
  }
}

Via a webhook

Another, much better, approach is to let Reloadly make a callback upon operation end.

In this case you’ll have to:

  1. Create a public endpoint on your side, which can handle POST requests from Reloadly
  2. Register your endpoint in your Reloadly Dashboard
  3. Make the originating request

This way, you’ll save a lot of resources since you won’t need to have a constantly running process issuing trivial requests to Reloadly’s servers.

Instead, your application will be the one receiving one request for every relevant event.

In a way, using this scheme you get the best of both worlds, sync and async.

Once you have your endpoint created, registering it in Reloadly is easy. Just follow this instructions.

The third point, making the originating request, is exactly the same as in the previous section, so let’s have a closer look at the endpoint itself.

How to create the public endpoint

This part can be as easy or complex as you want it to though a few basic items must be in place:

  1. A webserver to host your recipient application
  2. Public access to such server
  3. A URL within the webserver to be used as webhook target

Here’s a PHP example of what it might look like:

<?php

http_response_code(400);

logMessage("Got a callback from Reloadly".PHP_EOL);

if ('POST' !== strtoupper($_SERVER['REQUEST_METHOD'])) {
    die('Only POST requests are acceptable');
}

$postBody = file_get_contents('php://input');

logMessage("Post body = '$postBody'".PHP_EOL);

try {
    $event = json_decode($postBody, true, 512, JSON_THROW_ON_ERROR);
    $eventType = $event['type'];
    logMessage("Event type = '$eventType'".PHP_EOL);

    switch ($eventType) {
        case 'airtime_transaction.status':
            http_response_code(200);

            $status = $event['data']['transaction']['status'];
            logMessage("Status = '$status'".PHP_EOL);
            doImportantStuff();

            break;
        default:
            logMessage('Unsupported eventType '.$eventType);
    }

    echo json_encode(['received' => true]);
} catch (\Exception $exception) {
    logMessage('POST body wasn\'t json');
}

function logMessage(string $message): void
{
    error_log(date('[Y-m-d H:i:s] - '.$message));
}

function doImportantStuff()
{
    // Fill in the blanks
}

At the point of this writing the only available event you can expect from Reloadly is airtime_transaction.status but that will most certainly change soon so keep this resource on your radar and, if you want to have a broader look at Reloadly’s webhooks here is where you should go next.

A couple of things to keep in mind when building your endpoint:

  • The requests you’ll be receiving will be POST
  • The details of the event will be sent as json through the post body
  • The structure of the post body is similar to:
{
  "id": 1,
  "type": "airtime_transaction.status",
  "liveMode": false,
  "date": "2022-02-28 08:07:00",
  "data": {
    "code": null,
    "message": null,
    "status": "SUCCESSFUL",
    "transaction": {
      "transactionId": 1,
      "status": "SUCCESSFUL",
      "operatorTransactionId": null,
      "customIdentifier": null,
      "recipientPhone": "50936377111",
      "recipientEmail": null,
      "senderPhone": "13056154908",
      "countryCode": "HT",
      "operatorId": 173,
      "operatorName": "Digicel Haiti",
      "discount": 1.2,
      "discountCurrencyCode": "USD",
      "requestedAmount": 10,
      "requestedAmountCurrencyCode": "USD",
      "deliveredAmount": 1030.07,
      "deliveredAmountCurrencyCode": "HTG",
      "transactionDate": "2022-02-28 03:06:58",
      "pinDetail": null,
      "balanceInfo": {
        "oldBalance": 1000.00000,
        "newBalance": 991.20000,
        "currencyCode": "USD",
        "currencyName": "US Dollar",
        "updatedAt": "2022-02-28 08:06:58"
      }
    }
  }
}

For more details read this article.

This might also interest you:

Content by developers to developers.

Subscribe to The Monthly Reload for Developers and start receiving all the developers’ updates.

The Monthly Reload: the newsletter for you

Subscribe to our Newsletter and don’t miss any news about our industry and products.

It’s time to build, make your first API call today