USSD charge

Hey👋. We recommend checking out the overview to understand the basics of direct charge first. This guide assumes you've read that.

Direct USSD charge allows you to collect payments via USSD. With USSD charge, you call our API to create a charge, then your customer completes the payment by dialling their bank's USSD code on their mobile phone. Once the payment is completed, we'll notify you via webhook.

Initiating the payment

First, you'll need the customer's payment details. Since it's a USSD transaction, the only detail you need is the account_bank, the code matching the customer's bank. You can provide a simple UI for the user to select their bank.

Supported banks

USSD payments are currently only supported for the following banks:

  • Access bank (044)
  • Ecobank (050)
  • Fidelity bank (070)
  • First bank of Nigeria (011)
  • First city monument bank (FCMB) (214)
  • Guaranty trust bank (058)
  • Heritage bank (030)
  • Keystone bank (082)
  • Stanbic IBTC bank (221)
  • Sterling bank (232)
  • Union bank (032)
  • United bank for Africa (UBA) (033)
  • Unity Bank (215)
  • VFD microfinance bank (090110)
  • Wema bank (035)
  • Zenith bank (057)

Now, combine that with the rest of the payment details to create the payload and send to our charge USSD endpoint. You'll need to specify amount, currency, email and a unique tx_ref. You can also specify more details, such as the customer's fullname, phone_number, and custom meta information. See the endpoint documentation for details.

// Install with: npm i flutterwave-node-v3

const Flutterwave = require('flutterwave-node-v3');
const flw = new Flutterwave(process.env.FLW_PUBLIC_KEY, process.env.FLW_SECRET_KEY);
const payload = {
    account_bank: '058',
    amount: 7500,
    currency: 'NGN',
    email: 'chunkylover53@aol.com',
    tx_ref: this.generateTransactionReference(),
    fullname: 'Homer Simpson',
};
flw.Charge.ussd(payload)
    .then(console.log)
    .catch(console.log);
// Install with: composer require flutterwavedev/flutterwave-v3

$flw = new \Flutterwave\Rave(getenv('FLW_SECRET_KEY'));
// Set `PUBLIC_KEY` as an environment variable
$ussdChargeService = new \Flutterwave\Ussd();
$payload = [
    "account_bank" => '058',
    "amount" => 7500,
    "currency" => 'NGN',
    "email" => 'chunkylover53@aol.com',
    "tx_ref" => $this->generateTransactionReference(),
    "fullname" => 'Homer Simpson',
];
$response = $ussdChargeService->ussd($payload);
print_r($response);
# Install with: gem install flutterwave_sdk

require 'flutterwave_sdk'

flw = Flutterwave.new(ENV["FLW_PUBLIC_KEY"], ENV["FLW_SECRET_KEY"], ENV["FLW_ENCRYPTION_KEY"])
charge_ussd = USSD.new(flw)
payload = {
    account_bank: '058',
    amount: 7500,
    currency: 'NGN',
    email: 'chunkylover53@aol.com',
    fullname: 'Homer Simpson',
    tx_ref: generate_transaction_reference,
}
response = charge_ussd.initiate_charge payload
print response
curl --request POST \
   --url https://api.flutterwave.com/v3/charges?type=ussd \
   --header 'Authorization: Bearer YOUR_SECRET_KEY' \
   --header 'content-type: application/json' \
   --data '{
     "account_bank": "058",
     "amount": 7500,
     "currency": "NGN",
     "email": "chunkylover53@aol.com",
     "fullname": "Homer Simpson",
     "tx_ref": "BJUYU399fcd43"
}'

Handling the response

You'll get a response that looks like this:

{
    "status": "success",
    "message": "Charge initiated",
    "data": {
        "id": 276641033,
        "tx_ref": "BJUYU399fcd43",
        "flw_ref": "FLW693741590884271975",
        "device_fingerprint": "N/A",
        "amount": 7500,
        "charged_amount": 7500,
        "app_fee": 491.2,
        "merchant_fee": 0,
        "processor_response": "Transaction in progress",
        "auth_model": "USSD",
        "currency": "NGN",
        "ip": "N/A",
        "narration": "MerchantName",
+       "status": "pending",
        "payment_type": "ussd",
        "fraud_status": "ok",
        "charge_type": "normal",
        "created_at": "2020-05-31T00:17:51.000Z",
        "account_id": 17321,
        "customer": {
            "id": 210466255,
            "phone_number": null,
            "name": "Homer Simpson",
            "email": "chunkylover53@aol.com",
            "created_at": "2020-05-31T00:17:50.000Z"
        },
+       "payment_code": "9039"
    },
    "meta": {
        "authorization": {
            "mode": "ussd",
+           "note": "*889*767*9039#"
        }
    }
}

Let's break this down:

  • status is "successful", meaning that the charge was initiated successfully
  • data.status is "pending", meaning that the customer needs to authorize the transaction via USSD
  • data.payment_code contains a code for this transaction. For GTBank USSD payments, the user will be prompted to enter this after dialing the USSD code.
  • meta.authorization contains the important details to complete the payment. The note field holds the USSD code that the customer needs to dial from the mobile number linked to their bank account. Typically, it will be in the form *<bank_code>*xxx*<payment_code>#.

You should instruct your customer to dial the code in meta.authorization.note (and enter the payment_code if necessary to complete the payment.

Completing the payment

To complete the payment, the customer needs to dial the USSD code and authorize the charge.

Testing tip

In Test Mode, USSD transactions will automatically be paid (transition to "successful") after a few seconds.

We'll send you a webhook notification when the payment is completed. Here's what that looks like:

{
  "event": "charge.completed",
  "data": {
    "id": 2073992,
    "tx_ref": "BJUYU399fcd43",
    "flw_ref": "flwm3s4m0c1620380894041",
    "device_fingerprint": "N/A",
    "amount": 7500,
    "currency": "NGN",
    "charged_amount": 7500,
    "app_fee": 2000,
    "merchant_fee": 0,
    "processor_response": "Transaction Successful",
    "auth_model": "USSD",
    "ip": "::ffff:10.30.86.54",
    "narration": "MerchantName",
    "status": "successful",
    "payment_type": "ussd",
    "created_at": "2020-05-31T00:17:51.000Z",
    "account_id": 17321,
    "meta": null,
    "customer":{
      "id": 841600,
      "name": "Homer Simpson",
      "phone_number": "N/A",
      "email": "chunkylover53@aol.com",
      "created_at":"2021-05-07T09:48:13.000Z"
    }
  }
}

In your webhook handler, you can then verify the payment and credit your customer with whatever they paid for. See our guide to transaction verification for details.

const transactionId = payload.data.id;
await flw.Transaction.verify({ id: transactionId });
$transactionId = $payload['data']['id'];
$ussdChargeService->verifyTransaction($transactionId);
transaction_id = params[:data][:id]
charge_ussd.verify_charge transaction_id

You'll get a response that looks like this, and you can see that the data.status field is now "successful".

{
  "status": "success",
  "message": "Transaction fetched successfully",
  "data": {
    "id": 276641033,
    "tx_ref": "BJUYU399fcd43",
    "flw_ref": "FLW693741590884271975",
    "device_fingerprint": "N/A",
    "amount": 7500,
    "charged_amount": 7500,
    "app_fee": 2000,
    "merchant_fee": 0,
    "processor_response": "Transaction Successful",
    "auth_model": "USSD",
    "currency": "NGN",
    "ip": "::ffff:10.30.86.54",
    "narration": "MerchantName",
    "status": "successful",
    "payment_type": "ussd",
    "created_at": "2020-05-31T00:17:51.000Z",
    "account_id": 17321,
    "customer":{
      "id": 841600,
      "name": "Homer Simpson",
      "phone_number": "N/A",
      "email": "chunkylover53@aol.com",
      "created_at":"2021-05-07T09:48:13.000Z"
    },
    "meta": null,
    "amount_settled": 5500
  }
}

All done! 🎉🎉

Putting it together

Here's what a full implementation might look like:

// In an Express-like app:

// The route where we initiate payment
app.post('/pay', async (req, res) => {
    const payload = {
        account_bank: req.body.bank_code,
        amount: req.session.amount,
        currency: 'NGN',
        email: req.user.email,
        tx_ref: generateTransactionReference(),
        fullname: req.user.name,
    }
    const response = await flw.Charge.ussd(payload);
    if (response.status !== 'success') {
        // Something went wrong; bail
        res.redirect('back');
    }

    const ussdCode = response.meta.authorization.note;
    const paymentCode = response.payment_code;
    res.send(`
To complete the payment, dial <strong>${ussdCode}</strong> from the mobile number linked to your bank account.
If you're prompted for a payment code, enter <strong>${paymentCode}</strong>.
    `);
});

// Our webhook endpoint. See the guide on webhooks for a more thorough sample
app.post("/flw-webhook", (req, res) => {
    // Check signature, log payload, etc
    // Schedule a job that checks for the status of the payment
    transactionVerificationQueue.add({id: req.body.data.id});
    res.status(200).end();
});
// In a Laravel-like app:

// The route where we initiate payment
Route::post('/pay', function ($req) {
    $payload = [
        "account_bank" => $req->input('bank_code'),
        "amount" => session('amount'),
        "currency" => 'NGN',
        "email" => auth()->email,
        "tx_ref" => generateTransactionReference(),
        "fullname" => auth()->name,
    ];
    $response = $ussdChargeService->ussd($payload);
    if ($response['status'] !== 'success') {
        // Something went wrong; bail
        return redirect()->back();
    }

    $ussdCode = $response['meta']['authorization']['note'];
    $paymentCode = $response['payment_code'];
    return <<<HTML
To complete the payment, dial <strong>$ussdCode</strong> from the mobile number linked to your bank account.
If you're prompted for a payment code, enter <strong>$paymentCode</strong>.
HTML;
});


// Our webhook endpoint. See the guide on webhooks for a more thorough sample
Route::post('/flw-webhook', function ($req) {
    // Check signature, log payload, etc
    // Schedule a job that checks for the status of the payment
    dispatch(new CheckTransactionStatus($req->input('data')['id']);
    return response();
});
# In a Rails-like app:

# Handles POST /pay
# The route where we initiate payment
def pay
    payload = {
        account_bank: params[:bank_code],
        amount: session[:amount],
        currency: 'NGN',
        email: current_user.email,
        fullname: current_user.name,
        tx_ref: generate_transaction_reference,
    }
    response = charge_ussd.initiate_charge payload
    if response['status'] != 'success'
        # Something went wrong; bail
        return redirect_back
    end

    ussd_code = response['meta']['authorization']['note']
    payment_code = response['payment_code']
    render html: <<~HTML
      To complete the payment, dial <strong>#{ussd_code}</strong> from the mobile number linked to your bank account.
      If you're prompted for a payment code, enter <strong>#{payment_code}</strong>.
    HTML
end

# Our webhook endpoint. See the guide on webhooks for a more thorough sample
def flw_webhook
    # Check signature, log payload, etc
    # Schedule a job that checks for the status of the payment
    CheckTransactionStatus.perform_later params[:data][:id]
    head :ok
end
Loading...