Webhook
Subscribe to webhooks for real-time updates and event-driven interactions.
Create a webhook
From your Merchant Portal, go the the section Developers
> Webhooks
> Create
1. Setup your server
https://<your-website>/<your-webhook-endpoint>
Set your webhook listener URL HTTPS endpoint, and accept POST requests from our Webhooks server.
HTTPS protocol
Please note that the webhook sent from our system utilizes HTTPS protocol. If your server currently operates on HTTP the webhook transmission will not be successful.
2. Subscribe to an event
Order status
Result of FlexCharge's analysis on a transactions.
Event Name | Object | Description |
---|---|---|
order.completed | Order | Occurs whenever customer approved the offer and FlexCharge created the order for processing. |
order.cancelled | Order | Occurs whenever an order is cancelled either by the system or by the merchant. |
order.expired | Order | Occurs when an MIT transaction could not be rescued by FlexCharge by then end of the expiryDateUtc . |
order.refunded | Order | Occurs when the order was refunded. |
Payment status
Events related to payments or transfer (payouts).
Event Name | Object | Description |
---|---|---|
payment.chargeback.received | Payment | Triggered when a chargeback occurs by the customer's issuing bank. |
payout.created | Payout | A payout was created to send funds from FlexCharge's account to the merchant's. |
payout.updated | Payout | The status of a payout was updated: pending, completed, failed, or reversed. |
It is recommended to create one endpoint for all webhooks and not manage multiple endpoints
3. Handle incoming requests
FlexCharge webhook server will send event objects to the endpoint URL set in step 1.
Parse the event object to handle the incoming request accordingly by extracting the relevant information:
Field Name | Field Type | Description |
---|---|---|
Event | String | The name of the event that was triggered (in the case below, "order.completed") |
TimeStamp | String | The timestamp of when the event occurred, in ISO 8601 format |
ExternalOrderId | String | An ID representing the order in the external system |
OrderId | Guid | A unique ID assigned by FlexCharge to identify the order |
ConfirmationId | Guid | A unique ID assigned by FlexCharge to identify the confirmation of the order |
IsTestMode | Boolean | A flag that indicates whether the event occurred in test mode or production mode |
IsResent | Boolean | A flag that indicates whether the event was resent due to a failure |
Example webhook payload
{
"Event":"order.completed",
"TimeStamp":"2022-10-26T11:52:58.5131254Z",
"ExternalOrderId":"String",
"OrderId":"Guid",
"ConfirmationId":"Guid",
"IsTestMode":true,
"IsResent":false
"EventData":{
"DisputeDateTime":"2022-10-26T11:52:58.5131254Z",
"Reason":"customer_initiated",
"InitialTransactionReference":"91c352b8-ec2d-47ee-8324-521c09ca2ccc",
"Amount":"10000",
"Currency":"USD"
}
}
Return a 2xx response
Webhook validation
Time to response
Your endpoint must instantly return a successful status code (2xx) prior to any complex logic that could cause a timeout.
If FlexCharge doesn’t instantly receive a 2xx response status code for an event, we mark the event as failed and stop trying to send it to your endpoint. After multiple days, we email you about the misconfigured endpoint, and automatically disable it soon after if you haven’t addressed it.
FlexCharge signature
Every event FlexCharge sends to a webhook endpoint includes a signature generated through a SHA-512 hash-based message authentication code (HMAC).
To verify that the webhook is authentic and came from FlexCharge, your service needs to recreate the same hash using the same signing secret, and compare to the signature specified in the header x-fc-authorization
header included in the webhook payload.
POST /webhook HTTP/1.1
Host: example.com
Content-Type: application/json
"x-fc-authorization": "HMAC-SHA512 SignedHeaders=x-fc-nonce;x-fc-date;host;x-fc-content-sha512&Signature=+HXN8ZewgINLk+uC/UI92HSWmLK7gZOECPxOGEM91ATyfyzScMF/+osEK5B0UjO7OFqahDvesSo8jmUWMZtQnA==",
"x-fc-content-sha512": "pLs0Op5VWqQM3ZIumqC2NP6MDqcnwFN1znp/oCuw9LcYd8PtvLC8ProyPg8ZDadsRc36NskT3QGKn/PkNqwWfg==",
"x-fc-date": "Mon, 20 Mar 2023 17:16:40 GMT",
"x-fc-nonce": "5f1c2de28a76457c9cb79d1740f2260a",
"x-fc-signature": "SbzcEwAKsViWqrB8+suZMjOdadswbUjLHtIKjDQJYle31xbB8Vr0pVTDaNP28/y+NDynpyFyKKnXmWZy8uJVig==",
"content-length": "255",
{"Event":"order.completed","TimeStamp":"2023-03-20T17:16:40.898703Z","EventData":null,"ExternalOrderId":"a9735210-1349-49bf-bfde-b737dd07872a","OrderId":"ac9674ed-cbfe-49aa-bc8b-eb1d2b74c429","ConfirmationId":"22ACD1D9","IsTestMode":false,"IsResent":false}
If the hashes match, it means that the webhook came from FlexCharge and has not been tampered with, and your service can process it.
Hashing functions and samples
exports.ComputeContentHash = function (payload) {
var crypto = require('crypto');
var hash = crypto.createHash('sha512');
hash.write(payload);
hash.end();
return hash.read().toString('base64');
}
exports.ComputeSignature = function (secret, payload) {
var crypto = require('crypto');
var hash = crypto.createHash('sha512');
var secretByteArray = Buffer.from(secret, 'base64');
var hmac = crypto.createHmac('sha512', secretByteArray);
hmac.write(payload);
hmac.end();
return hmac.read().toString('base64');
}
exports.ComputeWebhookSignature = function (secret, host,
content, nonce, date) {
var contentHash = exports.ComputeContentHash(content);
var toSign = "POST\n" + nonce + ";" + date + ";" + host + ";" + contentHash;
return exports.ComputeSignature(secret, toSign);
}
exports.Test = function () {
// From FlexCharge Merchant Portal
var subscriberKey = "XRmKBxG5uvt1qWzqvp+T6CAbTo0MB89GTxXZD5cHA56RP7Mj4NbnHQOR1Y8uorUU9YQz8ujaVRUdm9vTSkPZSw==";
// Webhook received payload
var payload = "{\"Event\":\"order.completed\",\"TimeStamp\":\"2023-03-20T17:16:40.898703Z\",\"EventData\":null,\"ExternalOrderId\":\"a9735210-1349-49bf-bfde-b737dd07872a\",\"OrderId\":\"ac9674ed-cbfe-49aa-bc8b-eb1d2b74c429\",\"ConfirmationId\":\"22ACD1D9\",\"IsTestMode\":true,\"IsResent\":false}";
// Merchant's Webhook endpoint host
var host = "fctestwebhook.free.beeceptor.com";
// From webhook's headers:
//"x-fc-nonce" header
var nonce = "5f1c2de28a76457c9cb79d1740f2260a";
//"x-fc-date" header
var date = "Mon, 20 Mar 2023 17:16:40 GMT";
//from "x-fc-authorization" header
var flexChargeAuthorization = "HMAC-SHA512 SignedHeaders=x-fc-nonce;x-fc-date;host;x-fc-content-sha512&Signature=+HXN8ZewgINLk+uC/UI92HSWmLK7gZOECPxOGEM91ATyfyzScMF/+osEK5B0UjO7OFqahDvesSo8jmUWMZtQnA==";
// Extracting signature from x-fc-authorization header
var signature = flexChargeAuthorization.substr(flexChargeAuthorization.indexOf("Signature=") + 10);
// Calculating signature
var recalculatedSignature = exports.ComputeWebhookSignature(subscriberKey, host, payload, nonce, date);
var verified = recalculatedSignature == signature;
console.log("Signature verification result: " + verified);
}
exports.Test();
using System;
using System.Linq;
using System.Net.Http.Headers;
using System.Security.Cryptography;
using System.Text;
using FlexCharge.Webhooks.Services;
namespace FlexCharge.Webhooks.SignatureVerificationSample;
public static class Program
{
public static void TestMain(string[] args)
{
// From FlexCharge Merchant Portal
string subscriberKey = "XRmKBxG5uvt1qWzqvp+T6CAbTo0MB89GTxXZD5cHA56RP7Mj4NbnHQOR1Y8uorUU9YQz8ujaVRUdm9vTSkPZSw==";
// Webhook received payload
//var payload = message.Content.ReadAsStringAsync().Result;
var payload = "{\"Event\":\"order.completed\",\"TimeStamp\":\"2023-03-20T17:16:40.898703Z\",\"EventData\":null,\"ExternalOrderId\":\"a9735210-1349-49bf-bfde-b737dd07872a\",\"OrderId\":\"ac9674ed-cbfe-49aa-bc8b-eb1d2b74c429\",\"ConfirmationId\":\"22ACD1D9\",\"IsTestMode\":true,\"IsResent\":false}";
// Merchant's Webhook endpoint host
string host = "fctestwebhook.free.beeceptor.com";
// From webhook's headers:
//var nonce = headers.GetValues("x-fc-nonce").First();
var nonce = "5f1c2de28a76457c9cb79d1740f2260a";
//var date = headers.GetValues("x-fc-date").First();
var date = "Mon, 20 Mar 2023 17:16:40 GMT";
//var var flexChargeAuthorization = headers.GetValues("x-fc-authorization").First();
var flexChargeAuthorization = "HMAC-SHA512 SignedHeaders=x-fc-nonce;x-fc-date;host;x-fc-content-sha512&Signature=+HXN8ZewgINLk+uC/UI92HSWmLK7gZOECPxOGEM91ATyfyzScMF/+osEK5B0UjO7OFqahDvesSo8jmUWMZtQnA==";
// Extracting signature from x-fc-authorization header
var signature = flexChargeAuthorization.Substring(flexChargeAuthorization.IndexOf("Signature=") + 10);
// Calculating signature
var recalculatedSignature = HMACSHA512.ComputeWebhookSignature(subscriberKey, host, payload, nonce, date);
var verified = recalculatedSignature == signature;
Console.WriteLine($"Signature verification result: {verified}");
}
}
public class HMACSHA512
{
public static string ComputeContentHash(string content, string hashAlgorithmName = "SHA512")
{
using (var hashAlgorithm = HashAlgorithm.Create(hashAlgorithmName))
{
byte[] hashedBytes = hashAlgorithm.ComputeHash(Encoding.UTF8.GetBytes(content));
return Convert.ToBase64String(hashedBytes);
}
}
public static string ComputeSignature(string stringToSign, string secret)
{
using (var hmacsha512 = new System.Security.Cryptography.HMACSHA512(Convert.FromBase64String(secret)))
{
var bytes = Encoding.UTF8.GetBytes(stringToSign);
var hashedBytes = hmacsha512.ComputeHash(bytes);
return Convert.ToBase64String(hashedBytes);
}
}
public static string ComputeWebhookSignature(string subscriberKey, string host,
string payload, string nonce, string date)
{
var contentHash = ComputeContentHash(payload);
var toSign = $"POST\n{nonce};{date};{host};{contentHash}";
return ComputeSignature(toSign, subscriberKey);
}
public static bool VerifyWebhookSignature(Uri webhookEndpoint, HttpHeaders headers,
string payload, string subscriberKey)
{
var nonce = headers.GetValues("x-fc-nonce").First();
var date = headers.GetValues("x-fc-date").First();
var flexChargeAuthorization = headers.GetValues("x-fc-authorization").First();
var signature = flexChargeAuthorization.Substring(flexChargeAuthorization.IndexOf("Signature=") + 10);
var recalculatedSignature = HMACSHA512.ComputeWebhookSignature(subscriberKey, webhookEndpoint.Host, payload, nonce, date);
return recalculatedSignature == signature;
}
}
If the hashes do not match, it means that the webhook payload has been modified or was not sent by FlexCharge, and your service should discard the webhook to prevent any potential security issues.
Updated about 2 hours ago