The Quab Express API is only available for users with corporate accounts.

The Quab Express API is organized around REST. Our API has predictable resource-oriented URLs, accepts JSON-encoded request bodies, returns JSON-encoded responses, and uses standard HTTP response codes, authentication, and verbs.

You can use the Quab Express API in test mode, which doesn’t affect your live data or interact with the package delivery networks. The API key you use to authenticate the request determines whether the request is live mode or test mode.

The Quab Express API doesn’t support bulk updates. You can work on only one object per request.

The Quab Express API differs for every account as we release new versions and tailor functionality.

Base Code

https://api.quabexpress.com

The Quab Express API uses API keys to authenticate requests. You can view and manage your API keys in the Quab Express Dashboard.

Test mode public keys have the prefix pk_test_ and live mode secret keys have the prefix pk_live_.

Test mode secret keys have the prefix sk_test_ and live mode secret keys have the prefix sk_live_. Both public and security keys need to be kept secure.

Your API keys carry many privileges, so be sure to keep them secure! Do not share your secret API keys in publicly accessible areas such as GitHub, client-side code, and so forth.

All API requests must be made over HTTPS. Calls made over plain HTTP will fail. API requests without authentication will also fail.

Your API Key

A sample test API key is included in all the examples here, so you can test any example right away. Do not submit any personally identifiable information in requests made with this key.

To test requests using your account, replace the sample API key with your actual API key or sign in.

Your API Keys are used to generate an authentication token that the API uses to access and make other API transactions. To generate these tokens, you will need to base64 encode your public keys and secret key and attach it to Authorization header as a Basic token of a GET request to Quab Express OAuth end point.

Request Parameters

  • grant_type

    enum

    The client_credentials grant type is supported.

Response Parameters

  • access_token

    nullable string

    Access token to access other APIs

  • expiry_in

    nullable integer

    Token expiry time in seconds

Note

The authorization token given in this request should be added in the headers of all other requests when using the Quab Express API. When the requests are made after the expiry time has elapsed will generate an error which will require the user to generate a new authorization token

Authorization End Point

https://api.quabexpress.com/oauth/v1/generate?grant_type=client_credentials

Authorzation Request


let unirest = require('unirest');
let req = unirest('GET', 'https://api.quabexpress.com/oauth/v1/generate?grant_type=client_credentials')
.headers({ 'Authorization': 'Basic eWFpMjlKSkpOTUZCTmFuZ0M5b1l1UDcxQTJRZU91UEQ6UmQxWFFPNjFFaVV5bUhxaA==' })
.send()
.end(res => {
    if (res.error) throw new Error(res.error);
    console.log(res.raw_body);
});
let headers = new Headers();
headers.append("Authorization", "Basic eWFpMjlKSkpOTUZCTmFuZ0M5b1l1UDcxQTJRZU91UEQ6UmQxWFFPNjFFaVV5bUhxaA==");
fetch("https://api.quabexpress.com/oauth/v1/generate?grant_type=client_credentials", { headers })
  .then(response => response.text())
  .then(result => console.log(result))
  .catch(error => console.log(error));
                                                    
var client = new RestClient("https://api.quabexpress.com/oauth/v1/generate?grant_type=client_credentials");
var request = new RestRequest(Method.GET);
request.AddHeader("Authorization", "Basic eWFpMjlKSkpOTUZCTmFuZ0M5b1l1UDcxQTJRZU91UEQ6UmQxWFFPNjFFaVV5bUhxaA==");
request.AddParameter("text/plain", "",  ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
Console.WriteLine(response.Content);
                                                    
                                                
                                                    
<?php
    $ch = curl_init('https://api.quabexpress.com/oauth/v1/generate?grant_type=client_credentials');
    curl_setopt($ch, CURLOPT_HTTPHEADER, ['Authorization: Basic eWFpMjlKSkpOTUZCTmFuZ0M5b1l1UDcxQTJRZU91UEQ6UmQxWFFPNjFFaVV5bUhxaA==']);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    $response = curl_exec($ch);
    curl_close($ch);
    echo $response;
?>
                                                    
                                                
                                                    
OkHttpClient client = new OkHttpClient().newBuilder().build();
Request request = new Request.Builder()
  .url("https://sandbox.safaricom.co.ke/oauth/v1/generate?grant_type=client_credentials")
  .method("GET", null)
  .addHeader("Authorization", "Basic eWFpMjlKSkpOTUZCTmFuZ0M5b1l1UDcxQTJRZU91UEQ6UmQxWFFPNjFFaVV5bUhxaA==")
  .build();
Response response = client.newCall(request).execute();
                                                    
                                                
                                                    
CURL *curl;
CURLcode res;
curl = curl_easy_init();
​
struct curl_slist *headers = NULL;
headers = curl_slist_append(headers, "Authorization: Basic eWFpMjlKSkpOTUZCTmFuZ0M5b1l1UDcxQTJRZU91UEQ6UmQxWFFPNjFFaVV5bUhxaA==");
​
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "GET");
curl_easy_setopt(curl, CURLOPT_URL, "https://api.quabexpress.com/oauth/v1/generate?grant_type=client_credentials");
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
​
res = curl_easy_perform(curl);
curl_easy_cleanup(curl);
                                                    
                                                
                                                    
import Foundation
​
var semaphore = DispatchSemaphore (value: 0)
​
var request = URLRequest(url: URL(string: "https://api.quabexpress.com/oauth/v1/generate?grant_type=client_credentials")!,timeoutInterval: Double.infinity)
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("Basic eWFpMjlKSkpOTUZCTmFuZ0M5b1l1UDcxQTJRZU91UEQ6UmQxWFFPNjFFaVV5bUhxaA==", forHTTPHeaderField: "Authorization")
​
request.httpMethod = "GET"
​
let task = URLSession.shared.dataTask(with: request) { data, response, error in
  guard let data = data else {
    print(String(describing: error))
    return
  }
  print(String(data: data, encoding: .utf8)!)
  semaphore.signal()
}
​
task.resume()

semaphore.wait()
                                                    
                                                

Authorization Response Body

                                                
{    
   "access_token": "c9SQxWWhmdVRlyh0zh8gZDTkubVF",
   "expires_in":"3599"
}
                                                
                                            

Quab Express uses conventional HTTP response codes to indicate the success or failure of an API request. In general: Codes in the 2xx range indicate success. Codes in the 4xx range indicate an error that failed given the information provided (e.g., a required parameter was omitted, a charge failed, etc.). Codes in the 5xx range indicate an error with Quab Express’ servers (these are rare).

Some 4xx errors that could be handled programmatically (e.g., a card is declined) include an error code that briefly explains the error reported.

Attributes

  • status

    string

    The name of the status of the request. In this case, it will be, Error

  • message

    nullable string

    A human-readable message providing more details about the error.

  • action

    nullable string

    A short string indicating how to proceed with an error.

Http status code summary

200 OK Everything worked as expected
400 Bad Request The request was unacceptable, often due to missing a required parameter.
401 Unauthorized No valid API key provided.
402 Request Failed The parameters were valid but the request failed.
403 Forbidden The API key doesn't have permissions to perform the request.
404 Not Found The requested resource doesn't exist.
409 Conflict The request conflicts with another request (perhaps due to using the same idempotent key).
424 External Dependency Failed The request couldn't be completed due to a failure in a dependency external to Quab Express.
429 Too Many Requests Too many requests hit the API too quickly. We recommend an exponential backoff of your requests.
500, 502, 503, 504 Server Errors Something went wrong on Quab Express' end. (These are rare.)

Error Response

                                                
{
  "status": "Error",
  "message": "Authentication not set",
  "action": "Add authentication to your request"
}
                                                
                                            

To create a new order, the user needs to send a POST request to create order endpoint.

The request body should be a JSON-encoded string that specifys the details of the new delivery order.

Request Parameters

  • command

    enum

    The action to be taken by the API. The only accepted value is quote

  • from

    enum

    The point of origin of the package. This can be CUSTOM_ADDRESS,FULFILLMENT_CENTRE or BUSINESS_ADDRESS

  • sender

    nullable sender object

    Details of the sender as an object. This object contains the following parameters; name,phoneNumber,emailAddress and location. It can be null only if from parameter is set to FULFILLMENT_CENTRE or BUSINESS_ADDRESS.

  • recipient

    recipient object

    Details of the package recipent as an object. This object contains the following parameters; name,phoneNumber,emailAddress and location. Check out Recipient object

  • products

    object array

    The products to be shipped as an array. The array elements is made up ofsku, name and quantityThis array must have a maximum of 20 products. Check out product object.

  • isExpress

    integer

    The kind of delivery service. If the delivery order is supposed to be an Express mail use 1, otherwise use 0 for ordinary package delivery.

  • requiresSignature

    integer

    The kind of delivery service. If the delivery order requires the recipient to provide a signature on delivery use 1, otherwise use 0 for ordinary package delivery.

Sender / Recipient Object

  • name

    string

    The name of the person that will send or pick up the package. The maximum string length is 100 characters long.

  • phoneNumber

    numeric

    The phone number of the person or entity that sends or receieves the package. This should be a valid number that incldes the country code.

  • emailAddress

    nullable string

    The email address of the person or entity that sends or receives the package. The emailAddress should be valid and functional. This parameter is nullable

  • location

    location object

    This is the pick up or delivery point location. The object must have 3 parameters; address, latitude and longitude. Check out the location object.

Location Object

  • address

    string

    The name of the person that will send or pick up the package. The maximum string length is 100 characters long.

  • latitude

    numeric

    The phone number of the person or entity that sends or receieves the package. This should be a valid number that incldes the country code.

  • longitude

    nullable string

    The email address of the person or entity that sends or receives the package. The emailAddress should be valid and functional. This parameter is nullable

Response Parameters

  • result

    result object

    Result of the create order request. Check out the result object

  • data

    nullable quotation object

    A quotation of the delivery order as an object. This will be null only if the request was not successful.

quotation data object

  • quotationID

    string

    Identification string of the quote

  • shipmentID

    string

    Shipment number of the package. This is to be used on the packaging label for identification of the shipment.

  • expiry

    integer

    Time in UNIX timestamp beyond which the quote will have expired. When this time is reached, a new quote should be generated.

  • charges

    charges object

    Details of the charges to be imposed on the delivery charges. This object contains ID,amount and chargesItems

charges object

  • ID

    string

    Identification string of the quote

  • amount

    string

    Total amount charged in Kenyan shillings.

  • chargeItems

    charge item object array

    Array of the charges imposed on the delivery service. Check out the charge item

charge item object

  • name

    string

    The name of the charge

  • amount

    string

    Total amount charged in Kenyan shillings.

Authorzation Request

Unirest.setTimeouts(0, 0);
HttpResponse response = Unirest.get("https://api.quabexpress.com/order/v1/create")
  .header("Content-Type", "application/json")
  .header("Authorization", "Bearer 0440f5ef43b44e0904ec72516b35c2ca")
  .body("{\r\n    \"command\":\"quote\",\r\n    \"from\":\"CUSTOM_ADDRESS\",\r\n    \"sender\":{\r\n        \"name\":\"Linus Laflamme\",\r\n        \"phoneNumber\":\"+254741051051\",\r\n        \"emailAddress\":\"[email protected]\",\r\n        \"location\":{\r\n            \"address\":\"South C, Nairobi\",\r\n            \"latitude\":\"\",\r\n            \"longitude\":\"\"\r\n        }\r\n    },\r\n    \"recipient\":{\r\n        \"name\":\"Linus Laflamme\",\r\n        \"phoneNumber\":\"+254741051051\",\r\n        \"emailAddress\":\"\",\r\n        \"location\":{\r\n            \"address\":\"Kilimani, Nairobi\",\r\n            \"latitude\":\"\",\r\n            \"longitude\":\"\"\r\n        }\r\n    },\r\n    \"products\":[\r\n        {\r\n            \"sku\":\"GRE-1234\",\r\n            \"name\":\"Blue breeze wipes\",\r\n            \"quantity\": 3\r\n        }\r\n    ],\r\n    \"isExpress\":1,\r\n    \"requiresSignature\":0\r\n    \r\n}")
  .asString();
const myHeaders = new Headers();
myHeaders.append("Content-Type", "application/json");
myHeaders.append("Authorization", "Bearer 0440f5ef43b44e0904ec72516b35c2ca");

const raw = JSON.stringify({
  "command": "quote",
  "from": "CUSTOM_ADDRESS",
  "sender": {
    "name": "Linus Laflamme",
    "phoneNumber": "+254741051051",
    "emailAddress": "[email protected]",
    "location": {
      "address": "South C, Nairobi",
      "latitude": "",
      "longitude": ""
    }
  },
  "recipient": {
    "name": "Linus Laflamme",
    "phoneNumber": "+254741051051",
    "emailAddress": "",
    "location": {
      "address": "Kilimani, Nairobi",
      "latitude": "",
      "longitude": ""
    }
  },
  "products": [
    {
      "sku": "GRE-1234",
      "name": "Blue breeze wipes",
      "quantity": 3
    }
  ],
  "isExpress": 1,
  "requiresSignature": 0
});

const requestOptions = {
  method: "GET",
  headers: myHeaders,
  body: raw,
  redirect: "follow"
};

fetch("https://api.quabexpress.com/order/v1/create", requestOptions)
  .then((response) => response.text())
  .then((result) => console.log(result))
  .catch((error) => console.error(error));
                                                    
var options = new RestClientOptions("")
{
  MaxTimeout = -1,
};
var client = new RestClient(options);
var request = new RestRequest("https://api.quabexpress.com/order/v1/create", Method.Get);
request.AddHeader("Content-Type", "application/json");
request.AddHeader("Authorization", "Bearer 0440f5ef43b44e0904ec72516b35c2ca");
var body = @"{
" + "\n" +
@"    ""command"":""quote"",
" + "\n" +
@"    ""from"":""CUSTOM_ADDRESS"",
" + "\n" +
@"    ""sender"":{
" + "\n" +
@"        ""name"":""Linus Laflamme"",
" + "\n" +
@"        ""phoneNumber"":""+254741051051"",
" + "\n" +
@"        ""emailAddress"":""[email protected]"",
" + "\n" +
@"        ""location"":{
" + "\n" +
@"            ""address"":""South C, Nairobi"",
" + "\n" +
@"            ""latitude"":"""",
" + "\n" +
@"            ""longitude"":""""
" + "\n" +
@"        }
" + "\n" +
@"    },
" + "\n" +
@"    ""recipient"":{
" + "\n" +
@"        ""name"":""Linus Laflamme"",
" + "\n" +
@"        ""phoneNumber"":""+254741051051"",
" + "\n" +
@"        ""emailAddress"":"""",
" + "\n" +
@"        ""location"":{
" + "\n" +
@"            ""address"":""Kilimani, Nairobi"",
" + "\n" +
@"            ""latitude"":"""",
" + "\n" +
@"            ""longitude"":""""
" + "\n" +
@"        }
" + "\n" +
@"    },
" + "\n" +
@"    ""products"":[
" + "\n" +
@"        {
" + "\n" +
@"            ""sku"":""GRE-1234"",
" + "\n" +
@"            ""name"":""Blue breeze wipes"",
" + "\n" +
@"            ""quantity"": 3
" + "\n" +
@"        }
" + "\n" +
@"    ],
" + "\n" +
@"    ""isExpress"":1,
" + "\n" +
@"    ""requiresSignature"":0
" + "\n" +
@"    
" + "\n" +
@"}";
request.AddStringBody(body, DataFormat.Json);
RestResponse response = await client.ExecuteAsync(request);
Console.WriteLine(response.Content);
                                                    
                                                
                                                    
<?php 
$curl = curl_init();
curl_setopt_array($curl, array(
  CURLOPT_URL => 'https://api.quabexpress.com/order/v1/create',
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_ENCODING => '',
  CURLOPT_MAXREDIRS => 10,
  CURLOPT_TIMEOUT => 0,
  CURLOPT_FOLLOWLOCATION => true,
  CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
  CURLOPT_CUSTOMREQUEST => 'GET',
  CURLOPT_POSTFIELDS =>'{
    "command":"quote",
    "from":"CUSTOM_ADDRESS",
    "sender":{
        "name":"Linus Laflamme",
        "phoneNumber":"+254741051051",
        "emailAddress":"[email protected]",
        "location":{
            "address":"South C, Nairobi",
            "latitude":"",
            "longitude":""
        }
    },
    "recipient":{
        "name":"Linus Laflamme",
        "phoneNumber":"+254741051051",
        "emailAddress":"",
        "location":{
            "address":"Kilimani, Nairobi",
            "latitude":"",
            "longitude":""
        }
    },
    "products":[
        {
            "sku":"GRE-1234",
            "name":"Blue breeze wipes",
            "quantity": 3
        }
    ],
    "isExpress":1,
    "requiresSignature":0
    
}',
  CURLOPT_HTTPHEADER => array(
    'Content-Type: application/json',
    'Authorization: Bearer 0440f5ef43b44e0904ec72516b35c2ca'
  ),
));

$response = curl_exec($curl);

curl_close($curl);
echo $response;


?>
                                                    
                                                
                                                    
OkHttpClient client = new OkHttpClient().newBuilder()
  .build();
MediaType mediaType = MediaType.parse("application/json");
RequestBody body = RequestBody.create(mediaType, "{\r\n    \"command\":\"quote\",\r\n    \"from\":\"CUSTOM_ADDRESS\",\r\n    \"sender\":{\r\n        \"name\":\"Linus Laflamme\",\r\n        \"phoneNumber\":\"+254741051051\",\r\n        \"emailAddress\":\"[email protected]\",\r\n        \"location\":{\r\n            \"address\":\"South C, Nairobi\",\r\n            \"latitude\":\"\",\r\n            \"longitude\":\"\"\r\n        }\r\n    },\r\n    \"recipient\":{\r\n        \"name\":\"Linus Laflamme\",\r\n        \"phoneNumber\":\"+254741051051\",\r\n        \"emailAddress\":\"\",\r\n        \"location\":{\r\n            \"address\":\"Kilimani, Nairobi\",\r\n            \"latitude\":\"\",\r\n            \"longitude\":\"\"\r\n        }\r\n    },\r\n    \"products\":[\r\n        {\r\n            \"sku\":\"GRE-1234\",\r\n            \"name\":\"Blue breeze wipes\",\r\n            \"quantity\": 3\r\n        }\r\n    ],\r\n    \"isExpress\":1,\r\n    \"requiresSignature\":0\r\n    \r\n}");
Request request = new Request.Builder()
  .url("https://api.quabexpress.com/order/v1/create")
  .method("GET", body)
  .addHeader("Content-Type", "application/json")
  .addHeader("Authorization", "Bearer 0440f5ef43b44e0904ec72516b35c2ca")
  .build();
Response response = client.newCall(request).execute();
                                                    
                                                
                                                    
CURL *curl;
CURLcode res;
curl = curl_easy_init();
if(curl) {
  curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "GET");
  curl_easy_setopt(curl, CURLOPT_URL, "https://api.quabexpress.com/order/v1/create");
  curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
  curl_easy_setopt(curl, CURLOPT_DEFAULT_PROTOCOL, "https");
  struct curl_slist *headers = NULL;
  headers = curl_slist_append(headers, "Content-Type: application/json");
  headers = curl_slist_append(headers, "Authorization: Bearer 0440f5ef43b44e0904ec72516b35c2ca");
  curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
  const char *data = "{\r\n    \"command\":\"quote\",\r\n    \"from\":\"CUSTOM_ADDRESS\",\r\n    \"sender\":{\r\n        \"name\":\"Linus Laflamme\",\r\n        \"phoneNumber\":\"+254741051051\",\r\n        \"emailAddress\":\"[email protected]\",\r\n        \"location\":{\r\n            \"address\":\"South C, Nairobi\",\r\n            \"latitude\":\"\",\r\n            \"longitude\":\"\"\r\n        }\r\n    },\r\n    \"recipient\":{\r\n        \"name\":\"Linus Laflamme\",\r\n        \"phoneNumber\":\"+254741051051\",\r\n        \"emailAddress\":\"\",\r\n        \"location\":{\r\n            \"address\":\"Kilimani, Nairobi\",\r\n            \"latitude\":\"\",\r\n            \"longitude\":\"\"\r\n        }\r\n    },\r\n    \"products\":[\r\n        {\r\n            \"sku\":\"GRE-1234\",\r\n            \"name\":\"Blue breeze wipes\",\r\n            \"quantity\": 3\r\n        }\r\n    ],\r\n    \"isExpress\":1,\r\n    \"requiresSignature\":0\r\n    \r\n}";
  curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data);
  res = curl_easy_perform(curl);
  curl_slist_free_all(headers);
}
curl_easy_cleanup(curl);

                                                    
                                                
                                                    
let parameters = "{\r\n    \"command\":\"quote\",\r\n    \"from\":\"CUSTOM_ADDRESS\",\r\n    \"sender\":{\r\n        \"name\":\"Linus Laflamme\",\r\n        \"phoneNumber\":\"+254741051051\",\r\n        \"emailAddress\":\"[email protected]\",\r\n        \"location\":{\r\n            \"address\":\"South C, Nairobi\",\r\n            \"latitude\":\"\",\r\n            \"longitude\":\"\"\r\n        }\r\n    },\r\n    \"recipient\":{\r\n        \"name\":\"Linus Laflamme\",\r\n        \"phoneNumber\":\"+254741051051\",\r\n        \"emailAddress\":\"\",\r\n        \"location\":{\r\n            \"address\":\"Kilimani, Nairobi\",\r\n            \"latitude\":\"\",\r\n            \"longitude\":\"\"\r\n        }\r\n    },\r\n    \"products\":[\r\n        {\r\n            \"sku\":\"GRE-1234\",\r\n            \"name\":\"Blue breeze wipes\",\r\n            \"quantity\": 3\r\n        }\r\n    ],\r\n    \"isExpress\":1,\r\n    \"requiresSignature\":0\r\n    \r\n}"
let postData = parameters.data(using: .utf8)

var request = URLRequest(url: URL(string: "https://api.quabexpress.com/order/v1/create")!,timeoutInterval: Double.infinity)
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("Bearer 0440f5ef43b44e0904ec72516b35c2ca", forHTTPHeaderField: "Authorization")

request.httpMethod = "GET"
request.httpBody = postData

let task = URLSession.shared.dataTask(with: request) { data, response, error in 
  guard let data = data else {
    print(String(describing: error))
    return
  }
  print(String(data: data, encoding: .utf8)!)
}

task.resume()

                                                    
                                                

Response Body

                                                
{
    "result": {
        "status": "Success",
        "message": "Successful",
        "action": ""
    },
    "data": {
        "quotationID": "U8KH4L",
        "shipmentID": "SM6W5A",
        "expiry": 1754340187,
        "charges": {
            "ID": "U8KH4L",
            "amount": 500,
            "chargeItems": [
                {
                    "name": "Shipping charges",
                    "amount": "500.00"
                },
                {
                    "name": "Discount",
                    "amount": "0.00"
                }
            ]
        }
    }
}
                                                
                                            

Once the user has received the quotation of the shipment delivery service, the user will have to confirm the quotation before it's expiry. The order will only be processed after confirmation. This request does the confirmation of the order request.

To confirm an order, a post request is sent to the API endpoint.

Request Parameters

  • command

    enum

    The action to be taken by the API. The only accepted value is confirm

  • quotation

    string

    The point of origin of the package. This can be CUSTOM_ADDRESS,FULFILLMENT_CENTRE or BUSINESS_ADDRESS

Response Parameters

  • result

    result object

    Result of the create order request. Check out the result object

  • data

    nullable quotation object

    A quotation of the delivery order as an object. This will be null only if the request was not successful.

Authorzation Request

var https = require('follow-redirects').https;
var fs = require('fs');

var options = {
  'method': 'GET',
  'hostname': 'api.quabexpress.com',
  'path': '/order/v1/confirm',
  'headers': {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer 0440f5ef43b44e0904ec72516b35c2ca'
  },
  'maxRedirects': 20
};

var req = https.request(options, function (res) {
  var chunks = [];

  res.on("data", function (chunk) {
    chunks.push(chunk);
  });

  res.on("end", function (chunk) {
    var body = Buffer.concat(chunks);
    console.log(body.toString());
  });

  res.on("error", function (error) {
    console.error(error);
  });
});

var postData = JSON.stringify({
  "command": "confirm",
  "quotation": "U8KH4L"
});

req.write(postData);

req.end();
const myHeaders = new Headers();
myHeaders.append("Content-Type", "application/json");
myHeaders.append("Authorization", "Bearer 0440f5ef43b44e0904ec72516b35c2ca");

const raw = JSON.stringify({
  "command": "confirm",
  "quotation": "U8KH4L"
});

const requestOptions = {
  method: "GET",
  headers: myHeaders,
  body: raw,
  redirect: "follow"
};

fetch("https://api.quabexpress.com/order/v1/confirm", requestOptions)
  .then((response) => response.text())
  .then((result) => console.log(result))
  .catch((error) => console.error(error));
                                                    
var options = new RestClientOptions("")
{
  MaxTimeout = -1,
};
var client = new RestClient(options);
var request = new RestRequest("https://api.quabexpress.com/order/v1/confirm", Method.Get);
request.AddHeader("Content-Type", "application/json");
request.AddHeader("Authorization", "Bearer 0440f5ef43b44e0904ec72516b35c2ca");
var body = @"{
" + "\n" +
@"    ""command"":""confirm"",
" + "\n" +
@"    ""quotation"":""U8KH4L""
" + "\n" +
@"}";
request.AddStringBody(body, DataFormat.Json);
RestResponse response = await client.ExecuteAsync(request);
Console.WriteLine(response.Content);
                                                    
                                                
                                                    
<?php

$curl = curl_init();

curl_setopt_array($curl, array(
  CURLOPT_URL => 'https://api.quabexpress.com/order/v1/confirm',
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_ENCODING => '',
  CURLOPT_MAXREDIRS => 10,
  CURLOPT_TIMEOUT => 0,
  CURLOPT_FOLLOWLOCATION => true,
  CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
  CURLOPT_CUSTOMREQUEST => 'GET',
  CURLOPT_POSTFIELDS =>'{
    "command":"confirm",
    "quotation":"U8KH4L"
}',
  CURLOPT_HTTPHEADER => array(
    'Content-Type: application/json',
    'Authorization: Bearer 0440f5ef43b44e0904ec72516b35c2ca'
  ),
));

$response = curl_exec($curl);

curl_close($curl);
echo $response;

?>
                                                    
                                                
                                                    
Unirest.setTimeouts(0, 0);
HttpResponse<String> response = Unirest.get("https://api.quabexpress.com/order/v1/confirm")
  .header("Content-Type", "application/json")
  .header("Authorization", "Bearer 0440f5ef43b44e0904ec72516b35c2ca")
  .body("{\r\n    \"command\":\"confirm\",\r\n    \"quotation\":\"U8KH4L\"\r\n}")
  .asString();

                                                    
                                                
                                                    
CURL *curl;
CURLcode res;
curl = curl_easy_init();
if(curl) {
  curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "GET");
  curl_easy_setopt(curl, CURLOPT_URL, "https://api.quabexpress.com/order/v1/confirm");
  curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
  curl_easy_setopt(curl, CURLOPT_DEFAULT_PROTOCOL, "https");
  struct curl_slist *headers = NULL;
  headers = curl_slist_append(headers, "Content-Type: application/json");
  headers = curl_slist_append(headers, "Authorization: Bearer 0440f5ef43b44e0904ec72516b35c2ca");
  curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
  const char *data = "{\r\n    \"command\":\"confirm\",\r\n    \"quotation\":\"U8KH4L\"\r\n}";
  curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data);
  res = curl_easy_perform(curl);
  curl_slist_free_all(headers);
}
curl_easy_cleanup(curl);
                                                    
                                                
                                                    
let parameters = "{\r\n    \"command\":\"confirm\",\r\n    \"quotation\":\"U8KH4L\"\r\n}"
let postData = parameters.data(using: .utf8)

var request = URLRequest(url: URL(string: "https://api.quabexpress.com/order/v1/confirm")!,timeoutInterval: Double.infinity)
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("Bearer 0440f5ef43b44e0904ec72516b35c2ca", forHTTPHeaderField: "Authorization")

request.httpMethod = "GET"
request.httpBody = postData

let task = URLSession.shared.dataTask(with: request) { data, response, error in 
  guard let data = data else {
    print(String(describing: error))
    return
  }
  print(String(data: data, encoding: .utf8)!)
}

task.resume()
                                                    
                                                

Response Body

                                                
{
    "result": {
        "status": "Success",
        "message": "Successful",
        "action": ""
    },
    "data": {
        "order": "R795RU2",
        "quotation": "U8KH4L",
        "shipment": "SM6W5A",
        "tracking": "QE0003936761KE",
        "recipient": {
            "name": "Linus Laflamme",
            "phone": "+254741051051",
            "address": "Kilimani, Nairobi"
        }
    }
}
                                                
                                            

Webhooks allow you to set up a notification system that can be used to receive updates on certain requests made to the Quab Express API.

After setting up a webhook url through your dashboard, our server will make requests to the url with payloads of the relevant events that take place to your account and/or order.

When your webhook URL receives an event, it needs to parse and acknowledge the event. Acknowledging an event means returning a 200 OK in the HTTP header. Without a 200 OK in the response header, events are sent for the next 72 hours:

Note

If you have extra tasks in your webhook function, you should return a 200 OK response immediately. Long-running tasks lead to a request timeout and an automatic error response from your server. Without a 200 OK response, the retry as described in the proceeding paragraph.

Since your webhook URL is publicly available, you need to verify that events originate from Quab Express and not a bad actor. There are two ways to ensure events to your webhook URL are from Quab Express:

Signature Validation

Events sent from Quab Express carry the x-paystack-signature header. The value of this header is a HMAC SHA256 signature of the event payload signed using your secret key. Verifying the header signature should be done before processing the event:

When your webhook URL receives an event, it needs to parse and acknowledge the event. Acknowledging an event means returning a 200 OK in the HTTP header. Without a 200 OK in the response header, events are sent for the next 72 hours:

Supported events

Here are the events we currently raise. We would add more to this list as we hook into more actions in the future.

Supported Webhook Events

Event Description
order.placed OK
order.confirmed OK
order.processed OK
order.dispatched OK
order.delivered OK
order.returned OK
order.cancelled OK

Authorzation Request


// Using Express
app.post("/my/webhook/url", function(req, res) {
    // Retrieve the request's body
    const event = req.body;
    // Do something with event
    res.send(200);
});

                                                    const http = require('http');

// Create an HTTP server
const server = http.createServer((req, res) => {
    let body = '';

    // Retrieve the request's body
    req.on('data', chunk => {
        body += chunk.toString(); // Convert Buffer to string
    });

    req.on('end', () => {
        try {
            // Parse the input as JSON
            const event = JSON.parse(body);
            // Do something with event
            console.log("Received event:", event); // Example: Print the event

            // Set the HTTP response code
            res.writeHead(200, { 'Content-Type': 'application/json' });
            res.end(JSON.stringify({ status: 'success' })); // Respond with a success message
        } catch (error) {
            console.error("Error parsing JSON:", error);
            res.writeHead(400, { 'Content-Type': 'application/json' });
            res.end(JSON.stringify({ error: 'Invalid JSON' })); // Respond with an error message
        }
    });
});

// Server listens on port 3000
server.listen(3000, () => {
    console.log('Server is running on http://localhost:3000');
});
                                                    
using System;
using System.IO;
using System.Net;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;

public class Program
{
    public static async Task Main(string[] args)
    {
        // Set up an HTTP listener
        var listener = new HttpListener();
        listener.Prefixes.Add("http://localhost:8080/"); // Replace with your desired URL

        try
        {
            listener.Start();
            Console.WriteLine("Listening for requests on http://localhost:8080/");

            while (true)
            {
                // Wait for a request
                var context = await listener.GetContextAsync();
                var request = context.Request;
                var response = context.Response;

                // Read the request body
                string requestBody;
                using (var reader = new StreamReader(request.InputStream, Encoding.UTF8))
                {
                    requestBody = await reader.ReadToEndAsync();
                }

                // Process the request body as JSON
                try
                {
                    // Deserialize the JSON
                    var eventData = JsonSerializer.Deserialize<JsonElement>(requestBody);

                    // Do something with eventData
                    Console.WriteLine("Received event:");
                    Console.WriteLine(JsonSerializer.Serialize(eventData, new JsonSerializerOptions { WriteIndented = true })); // Pretty print the JSON

                    // Set the response
                    response.StatusCode = (int)HttpStatusCode.OK; // 200 OK
                    response.ContentType = "application/json";
                    var responseString = JsonSerializer.Serialize(new { status = "success" }); // Create a success response
                    byte[] buffer = Encoding.UTF8.GetBytes(responseString);
                    response.ContentLength64 = buffer.Length;
                    var output = response.OutputStream;
                    await output.WriteAsync(buffer, 0, buffer.Length);
                    output.Close();
                }
                catch (JsonException ex)
                {
                    // Handle JSON parsing errors
                    Console.WriteLine($"Error parsing JSON: {ex.Message}");
                    response.StatusCode = (int)HttpStatusCode.BadRequest; // 400 Bad Request
                    response.ContentType = "application/json";
                    var errorResponse = JsonSerializer.Serialize(new { error = "Invalid JSON" });
                    byte[] buffer = Encoding.UTF8.GetBytes(errorResponse);
                    response.ContentLength64 = buffer.Length;
                    var output = response.OutputStream;
                    await output.WriteAsync(buffer, 0, buffer.Length);
                    output.Close();
                }
                catch (Exception ex)
                {
                    // Handle other errors
                    Console.WriteLine($"An error occurred: {ex.Message}");
                    response.StatusCode = (int)HttpStatusCode.InternalServerError; // 500 Internal Server Error
                    response.ContentType = "application/json";
                    var errorResponse = JsonSerializer.Serialize(new { error = "Internal Server Error" });
                    byte[] buffer = Encoding.UTF8.GetBytes(errorResponse);
                    response.ContentLength64 = buffer.Length;
                    var output = response.OutputStream;
                    await output.WriteAsync(buffer, 0, buffer.Length);
                    output.Close();
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"An error occurred: {ex.Message}");
        }
        finally
        {
            listener.Stop();
        }
    }
}
                                                    
                                                
                                                    
<?php
    // Retrieve the request's body and parse it as JSON
    $input = @file_get_contents("php://input");
    $event = json_decode($input);
    // Do something with $event
    http_response_code(200); // PHP 5.4 or greater
?>
                                                    
                                                
                                                    
JSONObject json = new JSONObject();
json.put("key", "value");

// Sending a POST request
HttpResponse<JsonNode> response = Unirest.post("http://localhost:3000/")
        .header("Content-Type", "application/json")
        .body(json)
        .asJson();

// Check the response status
if (response.getStatus() == 200) {
    System.out.println("Success: " + response.getBody());
} else {
    System.out.println("Error: " + response.getStatus() + " - " + response.getBody());
}
                                                    
                                                
                                                    
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <curl/curl.h> // For sending an HTTP response - you may need to install libcurl

// Structure to hold the response data (for libcurl)
struct ResponseData {
    char *data;
    size_t size;
};

// Callback function for libcurl to write the response data
size_t WriteCallback(void *contents, size_t size, size_t nmemb, void *userp) {
    size_t realsize = size * nmemb;
    struct ResponseData *resp = (struct ResponseData *)userp;

    char *ptr = realloc(resp->data, resp->size + realsize + 1);
    if (ptr == NULL) {
        // Out of memory!
        return 0;
    }

    resp->data = ptr;
    memcpy(&(resp->data[resp->size]), contents, realsize);
    resp->size += realsize;
    resp->data[resp->size] = 0;

    return realsize;
}


int main() {
    char *input = NULL;
    size_t input_size = 0;
    int ch;

    // Read input from stdin (standard input)
    // This is a basic way to read the input.  You might need to adapt this
    // depending on how the input is provided (e.g., from a file, network).
    while ((ch = getchar()) != EOF) {
        input = realloc(input, input_size + 1);
        if (input == NULL) {
            perror("realloc failed");
            free(input);
            return 1;
        }
        input[input_size++] = ch;
    }
    if (input_size > 0) {
        input[input_size] = '\0'; // Null-terminate the input string
    }

    // Parse the input as JSON (simplified - you'll likely want a proper JSON library)
    // This is a placeholder.  You'll need a JSON parsing library (e.g., cJSON, Jansson)
    // to correctly parse the JSON data.  I'll show a very basic example.
    if (input != NULL && input_size > 0) {
        printf("Received input: %s\n", input); // Basic output
        // **IMPORTANT:** Replace this with a proper JSON parsing library!
        // For example, using cJSON:
        // cJSON *json = cJSON_Parse(input);
        // if (json != NULL) {
        //     // Access JSON elements here
        //     cJSON_Delete(json);
        // } else {
        //     fprintf(stderr, "Error parsing JSON\n");
        // }
    } else {
        fprintf(stderr, "No input received.\n");
    }


    // Set the HTTP response code (requires libcurl or similar)
    // This example *sends* an HTTP response.  If you're just processing input,
    // you might not need this.

    CURL *curl;
    CURLcode res;
    struct ResponseData response;

    response.data = malloc(1);  // Allocate initial memory
    response.size = 0;
    response.data[0] = '\0';

    curl = curl_easy_init();
    if (curl) {
        curl_easy_setopt(curl, CURLOPT_URL, "your_callback_url_here"); // Replace with your URL
        curl_easy_setopt(curl, CURLOPT_POST, 1L);  // Use POST method
        curl_easy_setopt(curl, CURLOPT_POSTFIELDS, "{\"status\": \"success\"}"); // Replace with your data
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);

        res = curl_easy_perform(curl);
        if (res != CURLE_OK) {
            fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
        } else {
            //printf("Response: %s\n", response.data);  // Print the response (if any)
        }

        curl_easy_cleanup(curl);
    } else {
        fprintf(stderr, "curl_easy_init() failed\n");
    }

    free(response.data); // Free the allocated memory for the response

    free(input); // Free the allocated input buffer

    return 0;
}
                                                    
                                                
                                                    
import Foundation

// Retrieve the request's body
if let input = String(data: FileHandle.standardInput.availableData, encoding: .utf8) {
    // Parse the input as JSON
    if let eventData = input.data(using: .utf8),
    let event = try? JSONSerialization.jsonObject(with: eventData, options: []) {
        // Do something with event
        if let eventDictionary = event as? [String: Any] {
            // Access JSON data using eventDictionary
            print("Received event: \(eventDictionary)") // Example: Print the event
        } else if let eventArray = event as? [Any] {
            // Access JSON data using eventArray
            print("Received event: \(eventArray)") // Example: Print the event
        } else {
            print("Received event: \(event)") // If it's not a dictionary or array
        }
    } else {
        print("Error: Could not decode JSON.")
    }
} else {
    print("Error: Could not read input.")
}
                                                    
                                                

Signature Validation


const crypto = require('crypto');
const secret = process.env.SECRET_KEY;
// Using Express

app.post("/my/webhook/url", function(req, res) {

    //validate event
    const hash = crypto.createHmac('sha256', secret).update(JSON.stringify(req.body)).digest('hex');

    if (hash == req.headers['x-quab-express-signature']) {

    // Retrieve the request's body
    const event = req.body;

    // Do something with event  

    }
    res.send(200);

});
                                                    
using System;
using System.Security.Cryptography;
using System.Text;
using Newtonsoft.Json.Linq;


namespace HMacExample
{

  class Program {

    static void Main(string[] args) {

      String key = "YOUR_SECRET_KEY"; //replace with your quab express secret_key
      String jsonInput = "{"paystack":"request","body":"to_string"}"; //the json input

      String inputString = Convert.ToString(new JValue(jsonInput));

      String result = "";
      byte[] secretkeyBytes = Encoding.UTF8.GetBytes(key);
      byte[] inputBytes = Encoding.UTF8.GetBytes(inputString);
      using (var hmac = new HMACSHA256(secretkeyBytes))

      {

          byte[] hashValue = hmac.ComputeHash(inputBytes);
          result = BitConverter.ToString(hashValue).Replace("-", string.Empty);;

      }

      Console.WriteLine(result);

      String xQuabExpressSignature = ""; //put in the request's header value for x-quab-express-signature  

      if(result.ToLower().Equals(xQuabExpressSignature)) {

          // you can trust the event, it came from quab express
          // respond with the http 200 response immediately before attempting to process the response
          //retrieve the request body, and deliver value to the customer

      } else {
          // this isn't from Quab Express, ignore it

      }

    }

  }

}
                                                    
                                                
                                                    
<?php

  // only a post with quab express signature header gets our attention
  if ((strtoupper($_SERVER['REQUEST_METHOD']) != 'POST' ) || !array_key_exists('HTTP_X_QUAB_EXPRESS_SIGNATURE', $_SERVER) ) 
      exit();

  // Retrieve the request's body
  $input = @file_get_contents("php://input");

  define('QUAB_EXPRESS_SECRET_KEY','SECRET_KEY');

  // validate event do all at once to avoid timing attack

  if($_SERVER['HTTP_X_QUAB_EXPRESS_SIGNATURE'] !== hash_hmac('sha256', $input, QUAB_EXPRESS_SECRET_KEY))
      exit();

  http_response_code(200);
  // parse event (which is json string) as object
  // Do something - that will not take long - with $event
  $event = json_decode($input);

  exit();

?>
                                                    
                                                
                                                    
package hmacexample;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
import org.json.JSONException;
import org.json.JSONObject;


public class HMacExample {

  public static void main(String[] args) throws UnsupportedEncodingException, InvalidKeyException, NoSuchAlgorithmException, JSONException {

    //first you verify that this request came from quab express

    String key = "YOUR_SECRET_KEY"; //replace with your quab express secret_key    

    String rawJson = "{\"paystack\":\"request\",\"body":\"to_string\"}";

    JSONObject body = new JSONObject(rawJson);

    String result = "";

    String HMAC_SHA256 = "HmacSHA256";

    String xQuabExpressSignature = ""; //put in the request's header value for x-quab-express-signature

    

    byte [] byteKey = key.getBytes("UTF-8");

    SecretKeySpec keySpec = new SecretKeySpec(byteKey, HMAC_SHA256);

    Mac sha256_HMAC = Mac.getInstance(HMAC_SHA256);      

    sha256_HMAC.init(keySpec);

    byte [] mac_data = sha256_HMAC.

    doFinal(body.getBytes("UTF-8"));

    result = DatatypeConverter.printHexBinary(mac_data);

    if(result.toLowerCase().equals(xQuabExpressSignature)) {

      // you can trust the event, it came from paystack

      // respond with the http 200 response immediately before attempting to process the response

      //retrieve the request body, and deliver value to the customer

    }else{

      // this isn't from Quab Express, ignore it

    }  

  }

}