Webhooks
You may receive webhooks when certain order statuses are reached. This is an optional configuration that can reduce the number of API requests you need to send to Banxa.
Before you begin
- Log into Banxa Dashboard and configure your webhook URL's which Banxa will use to send your webhooks. You will be able to configure separate webhook URLs for both Sandbox and Production environments.
- Otherwise contact your Account Manager with your Webhook URLs.
Using Webhooks
After we have configured your webhook URLs, webhooks will be sent to this URL when an order has changed status. A webhook notification will be sent via HTTP POST in the below format:
{
"order_id": "e82c57b2cba367069dfef4f866c7bc87",
"status": "expired",
"status_date": "2024-01-31 12:48:36"
}
When you receive a webhook, you can then call the Get Order endpoint to retrieve detailed order information. This ensures that you do not receive calls from bad actors with incorrect information.
Securing Webhooks
Your webhooks will be sent with HMAC authentication. HMAC guarantees that the message is from Banxa and has not been altered on the way by a third party.
You can request an API Key and Secret from your Account Manager.
You will receive the webhook with an Authorization Header that is generated in the following format:
"Authorization: Bearer API Key:Signature:Nonce"
In order to decode the encryption, you will need to run the hash operation by computing the HMAC with the SHA256 hash function, and then perform an equality match.
Sample Code
Here is some sample code around how to call the API including the HMAC authentication. Note that the nonce should be a unix timestamp which is generated for every request
import requests
import time
import hmac
url = 'https://[PARTNER-NAME].banxa-sandbox.com'
key = '[YOUR_MERCHANT_KEY]'
secret = '[YOUR_MERCHANT_SECRET]'
def generateHmac(payload, nonce):
hmacCode = hmac.digest(secret, payload.encode('utf8'), 'SHA256')
print(payload)
print(hmacCode.hex())
return key + ':' + hmacCode.hex() + ':' + str(nonce)
package com.banxa;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.Formatter;
public class BanxaService {
private static final String BANXA_URL = "https://api.banxa-sandbox.com";
private static final String KEY = "[YOUR_MERCHANT_KEY]";
private static final String SECRET = "[YOUR_MERCHANT_SECRET]";
public String getHmac(String method, String query, String payload) throws Exception {
String nonce = String.valueOf(System.currentTimeMillis());
String data = method + "\n" +
query + "\n" +
nonce;
if (payload != null) {
data += "\n" + payload;
}
SecretKeySpec signingKey = new SecretKeySpec(SECRET.getBytes(), "HmacSHA256");
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(signingKey);
return KEY + ":" + toHexString(mac.doFinal(data.getBytes())) + ":" + nonce;
}
private String toHexString(byte[] bytes) {
Formatter formatter = new Formatter();
for (byte b : bytes) {
formatter.format("%02x", b);
}
return formatter.toString();
}
}
<?php
$url = "https://api.banxa-sandbox.com";
$key = '[YOUR_MERCHANT_KEY]';
$secret= '[YOUR_MERCHANT_SECRET]';
function generateHmac($payload, $nonce) {
$sign = hash_hmac('SHA256', $payload, $secret);
return $key.':'.$sign.':'.$nonce;
}
$nonce = time();
function generateHmac(signature, nonce) {
const crypto = require('crypto')
const key = '[YOUR_MERCHANT_KEY]'
const secret = '[YOUR_MERCHANT_SECRET]'
const localSignature = crypto.createHmac("SHA256", secret).update(signature).digest("hex");
return `${key}:${localSignature}:${nonce}`;
}
import CryptoKit
let key = "[YOUR_MERCHANT_KEY]"
let secret = "[YOUR_MERCHANT_SECRET]".data(using: .utf8)!
let secretKey = SymmetricKey(data: secret)
public func generateHmac(
method: String,
query: String,
data: Optional<String> = nil
) -> String {
let nonce = String(NSDate().timeIntervalSince1970).split(separator: ".")[0]
var payload = method + "\n" + query + "\n" + nonce
if((data) != nil) {
payload = payload + "\n" + (data!)
}
let payloadData = payload.data(using: .utf8)!
let hmac = HMAC<SHA256>.authenticationCode(for: payloadData, using: secretKey)
let hmacByteString = hmac.map { String(format: "%02hhx", $0) }.joined()
return key + ":" + hmacByteString + ":" + nonce
}
require 'openssl'
def generate_hmac(time, query, body, method = 'POST')
secret = API_SECRET
body_encoded = body.to_s if body
data = "#{method}\n#{query.to_s}\n#{time.to_s}\n#{body_encoded.to_s}"
digest = OpenSSL::Digest.new('sha256')
hmac = OpenSSL::HMAC.hexdigest(digest, secret, data)
"#{API_KEY}:#{hmac}:#{time.to_s}"
end
Note. We will use the URI path of the webhook endpoint provided and the payload specified above when generating the signature.
Webhook Events
A webhook will be triggered and sent to your URL on all Order status transitions. The full list of order statuses can be found here
Webhook Retry Mechanism
Automatic Retries for Failed Webhooks
The webhook retry mechanism ensures reliable delivery of event notifications. If Banxa does not receive a 200 OK response from your server after sending a webhook, the system will automatically re-send the same webhook multiple times. The payload of the webhook remains unchanged across all retry attempts.
Retry Interval:
Retries are sent at increasing intervals following a Fibonacci sequence: 1 second, 2 seconds, 3 seconds, 5 seconds, 8 seconds, 13 seconds, and so on. This approach helps to avoid overwhelming your server with rapid-fire retries while still ensuring timely delivery.
Maximum Retry Duration:
The retry process continues for a maximum of 2 hours. If your server does not respond with a 200 OK within this timeframe, Banxa will stop retrying the webhook.
Maximum Number of Retries:
You may receive up to 18 webhook retries over the 2-hour period.
Important: Ensure your server is configured to handle these retries gracefully and respond with a 200 OK as soon as possible to avoid unnecessary retries.
Updated 2 months ago