Webhooks
Subscribe to webhooks to get updates on your transactions.
Create a webhook
From your Merchant Portal, go the the section Developers > Webhooks> Create

1. Setup your server
Webhook payload formatsNote that FlexFactor webhook payloads are not sent in application/json format. Please ensure that your endpoint can ingest these text-based messages. The payload is in proper JSON notation, so it can be serialized and parsed upon receipt.
Enter your webhook listener URL HTTPS endpoint, and accept POST requests from our Webhooks server.
It is recommended to create one endpoint for all webhooks and not manage multiple endpoints
HTTPS protocolPlease 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. Select the to event you want to subscribe to
Click on the event you want to subscribe to, then click Save
Order status
Result of FlexFactor action or decision on an order.
| Event Name | Priority | Description |
|---|---|---|
order.refunded | CRITICAL | Occurs when the order was refunded. |
order.completed | Recommended | Occurs whenever customer approved the offer and FlexFactor created the order for processing. |
order.cancelled | Recommended | Occurs whenever an order is cancelled either by the system or by the merchant. |
order.expired | Recommended | Occurs when an MIT transaction could not be rescued by FlexFactor by then end of the expiryDateUtc. |
order.capturerequired | Informational | Occurs when the 'capture' flow is set for your configuration, and on all Sync MIT approvals — Sync MIT defaults to auth/capture regardless of transactionType. This status will also be returned by the /evaluate api response. |
See payload examples for critical and recommended webhooks.
Payment status
Events related to payments
| Event Name | Priority | Description |
|---|---|---|
payment.chargeback.received | CRITICAL | Triggered when a chargeback occurs by the customer's issuing bank. |
payment.chargeback.reversed | Recommended | Triggered when a previously received chargeback is reversed in the merchant's favor. |
payment.early_warning.received | Recommended | Triggered when an issuer-side early-warning signal (e.g. Visa RDR / Ethoca alert) is received before a formal chargeback. |
challenge.presented | Critical for challenge flows | Triggered when a challenge is presented to consumer. |
challenge.attempted | Critical for challenge flows | Triggered when a challenge is attempted by consumer. It doesn't indicate success or failure. |
challenge.passed | Critical for challenge flows | Triggered when when consumer successfully completes a challenge. |
challenge.failed | Critical for challenge flows | Triggered when when consumer fails to complete a challenge. |
See payload examples for critical webhooks.
Application status
Events related to onboarding and application statuses. Relevant for Partner Integration.
| Event Name | Description |
|---|---|
| application.submitted | Triggered when an application was submitted and change from DRAFT to SUBMITTED status |
| application.canceled | Triggered when an application was canceled and changed from any status to CANCELED |
| application.approved | Triggered when FlexFactor has approved a SUBMITTED application. |
| application.declined | Triggered when FlexFactor has declined a SUBMITTED application. |
| application.converted | Triggered when an application was converted to a merchant by FlexFactor after approval, changing from APPROVED to CONVERTED status. |
Application events use the single-L spellingcanceled, while order events use the double-L spellingcancelled. This is intentional — do not "fix" one to match the other.
3. Webhook payloads
FlexFactor webhook server will send event objects to the endpoint URL set in step 1.
Fields description
Parse the event object to handle the incoming request accordingly by extracting the relevant information:
| Field Name | Field Type | Description |
|---|---|---|
| IdempotencyKey | Guid | A unique ID per event delivery — use it to de-duplicate webhook processing |
| 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 |
| EventData | Object | Event-specific details (structure varies per event; may be null) |
| Mid | Guid | The FlexFactor merchant ID the event relates to |
| Pid | Guid | The FlexFactor partner ID the event relates to |
| ExternalOrderId | String | An ID representing the order in the external system |
| OrderId | Guid | A unique ID assigned by FlexFactor to identify the order |
| ConfirmationId | Guid | A unique ID assigned by FlexFactor 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 |
Payload examples
{
"IdempotencyKey": "e4567890-d123-4abc-5678-4abcdef56789",
"Event": "payment.chargeback.received",
"TimeStamp": "2024-11-19T01:42:04.5149433Z",
"EventData": {
"DisputeDateTime": "2024-11-18T23:20:56Z",
"Reason": "10.4 Dispute ref: 12345678-abcd-1234-efgh-567890abcdef",
"TransactionId": "abcdef12-3456-7890-abcd-ef1234567890",
"InitialTransactionReference": "00000000-0000-0000-0000-000000000000",
"Amount": 4295,
"Currency": "USD"
},
"Mid": "11223344-5566-7788-99aa-bbccddeeff00",
"Pid": "99887766-5544-3322-1100-aabbccddeeff",
"ExternalOrderId": "123456789012",
"OrderId": "abcdef12-3456-7890-abcd-ef1234567890",
"ConfirmationId": null,
"IsTestMode": false,
"IsResent": false
}
{
"IdempotencyKey": "a1234567-b890-4cde-5678-5abcdef67890",
"Event": "order.refunded",
"TimeStamp": "2024-11-20T10:37:08.7405574Z",
"EventData": {
"Timestamp": "2024-11-20T10:37:08.6493879Z",
"Message": "Rapid Dispute Resolution, refunding before it becomes a chargeback.",
"TransactionId": "abcdef12-3456-7890-abcd-ef1234567890",
"InitialTransactionReference": "00000000-0000-0000-0000-000000000000",
"Amount": 8215,
"Currency": "USD"
},
"Mid": "11223344-5566-7788-99aa-bbccddeeff00",
"Pid": "99887766-5544-3322-1100-aabbccddeeff",
"ExternalOrderId": "123456789012",
"OrderId": "abcdef12-3456-7890-abcd-ef1234567890",
"ConfirmationId": null,
"IsTestMode": false,
"IsResent": false
}
{
"IdempotencyKey": "a2345678-b910-4def-8123-2abcdef34567",
"Event": "order.completed",
"TimeStamp": "2024-11-19T18:00:00.0000000Z",
"EventData": null,
"Mid": "11223344-5566-7788-99aa-bbccddeeff00",
"Pid": "99887766-5544-3322-1100-aabbccddeeff",
"ExternalOrderId": "123456789012",
"OrderId": "abcdef12-3456-7890-abcd-ef1234567890",
"ConfirmationId": "12345678",
"IsTestMode": false,
"IsResent": true
}
{
"IdempotencyKey": "d1234567-e890-4bcd-91ef-1abcde345678",
"Event": "order.cancelled",
"TimeStamp": "2024-11-20T10:45:00.0000000Z",
"EventData": null,
"Mid": "11223344-5566-7788-99aa-bbccddeeff00",
"Pid": "99887766-5544-3322-1100-aabbccddeeff",
"ExternalOrderId": "123456789012",
"OrderId": "abcdef12-3456-7890-abcd-ef1234567890",
"ConfirmationId": null,
"IsTestMode": false,
"IsResent": true
}
{
"IdempotencyKey": "b3456789-c012-4fab-9345-3abcdef45678",
"Event": "order.expired",
"TimeStamp": "2024-11-19T16:30:00.0000000Z",
"EventData": null,
"Mid": "11223344-5566-7788-99aa-bbccddeeff00",
"Pid": "99887766-5544-3322-1100-aabbccddeeff",
"ExternalOrderId": "123456789012",
"OrderId": "abcdef12-3456-7890-abcd-ef1234567890",
"ConfirmationId": null,
"IsTestMode": false,
"IsResent": true
}
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 FlexFactor doesn’t instantly receive a 2xx response status code for an event, the delivery is marked as failed and is retried — redelivered events carry "IsResent": true so you can de-duplicate them on your side. If deliveries keep failing over multiple days, we email your technical contact about the misconfigured endpoint, and endpoints that continue to fail are automatically disabled.
Source IP allowlist
Webhook deliveries originate from the following FlexFactor IPs. If your ingress firewall restricts inbound webhook traffic, allow these addresses in addition to verifying the HMAC signature.
| Environment | Source IPs |
|---|---|
| Production | 18.233.184.33, 52.4.241.101 |
| Sandbox | 35.172.5.145, 3.211.16.180 |
The IP allowlist is a defense-in-depth control, not a replacement for signature verification. Always verify the x-fc-authorization HMAC on every received webhook.
FlexFactor signature
Every event FlexFactor 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 FlexFactor, 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}
Matching hashesIf the hashes match, it means that the webhook came from FlexFactor 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 FlexFactor 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 = "your.endpoint.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 flexFactorAuthorization = "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 = flexFactorAuthorization.substr(flexFactorAuthorization.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 FlexFactor.Webhooks.Services;
namespace FlexFactor.Webhooks.SignatureVerificationSample;
public static class Program
{
public static void TestMain(string[] args)
{
// From FlexFactor 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 flexFactorAuthorization = headers.GetValues("x-fc-authorization").First();
var flexFactorAuthorization = "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 = flexFactorAuthorization.Substring(flexFactorAuthorization.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 flexFactorAuthorization = headers.GetValues("x-fc-authorization").First();
var signature = flexFactorAuthorization.Substring(flexFactorAuthorization.IndexOf("Signature=") + 10);
var recalculatedSignature = HMACSHA512.ComputeWebhookSignature(subscriberKey, webhookEndpoint.Host, payload, nonce, date);
return recalculatedSignature == signature;
}
}
Non matching hashesIf the hashes do not match, it means that the webhook payload has been modified or was not sent by FlexFactor, and your service should discard the webhook to prevent any potential security issues.
Testing webhooks
All webhook testing happens in the Sandbox environment. Events generated in Sandbox carry "IsTestMode": true in the payload — your handler can use this flag to separate test traffic from production traffic.
Before you start
- Create a Sandbox webhook endpoint at portal-sandbox.flexfactor.io and subscribe it to all events while testing.
- If your firewall restricts inbound traffic, allow the Sandbox source IPs:
35.172.5.145,3.211.16.180. - Use the test cards to drive transaction outcomes.
4000002760003184and4444555566660030produce successful rescues; the challenge scenarios use4111111111111111with the special phone numbers listed on that page.
Inspecting payloadsBefore wiring up your real handler, you can point the endpoint at a request-capture service to inspect raw payloads and headers. Switch to your real HTTPS endpoint before testing signature verification — the signed string includes your endpoint host.
Triggering each event
Order status events
| Event | How to trigger in Sandbox |
|---|---|
order.completed | Submit an MIT rescue: POST /v1/evaluate with isMIT: true, isRecurring: true, expiryDateUtc set, and a success test card. The call returns SUBMITTED; the webhook fires when the rescue is approved. |
order.cancelled | Create an order as above, then call POST /v1/orders/{id}/cancel with an empty body {}. |
order.expired | Submit an MIT rescue with expiryDateUtc set to a near-term timestamp. The webhook fires when the rescue window lapses without an approval. |
order.capturerequired | Submit a rescue with transactionType: authorize and a success test card. Requires the authorize capability to be enabled on your Sandbox account — contact [email protected] if it is not. Also fires on Sync MIT approvals (Sync MIT defaults to auth/capture regardless of transactionType); Sync MIT requires back-end configuration on your account, and the next /evaluate call for the order automatically acts as the capture. |
order.refunded | Complete a rescue, then call POST /v1/orders/{id}/refund with amountToRefund (in dollars). |
Payment status events
| Event | How to trigger in Sandbox |
|---|---|
payment.chargeback.received | Chargebacks originate from the issuing bank and cannot be triggered through the API. Contact [email protected] to have a test chargeback raised against a completed Sandbox order. |
payment.chargeback.reversed | Raised by FlexFactor when a chargeback is won — request a test reversal through [email protected] alongside the test chargeback. |
payment.early_warning.received | Early-warning signals (Visa RDR / Ethoca) are issuer-side and cannot be triggered through the API — request a simulated alert through [email protected]. |
challenge.presented | Submit a CIT rescue with a challenge test card (4111111111111111, CVV 999, name Cure Tester, one of the special phone numbers). The /evaluate response returns CHALLENGE and the webhook fires. |
challenge.attempted / challenge.passed / challenge.failed | Fire as the cardholder progresses through the challenge. These require an integration that presents the challenge to the cardholder — contact FlexFactor support for guidance on driving the challenge flow in Sandbox. |
Application status events
Relevant for Partner Integration only.
| Event | How to trigger in Sandbox |
|---|---|
application.submitted | Submit an application — the application moves from DRAFT to SUBMITTED. |
application.canceled | Cancel an application in any pre-conversion status. |
application.approved / application.declined | Approval and decline are FlexFactor review actions — contact [email protected] to have a submitted Sandbox application approved or declined. |
application.converted | Fires when an approved application is converted to a merchant — request conversion of an approved Sandbox application through [email protected]. |
Verifying your handler
Work through this checklist before requesting certification:
- Immediate 2xx — your endpoint acknowledges before any processing logic runs.
- Signature verification — the
x-fc-authorizationHMAC validates with your Sandbox subscriber key (see FlexFactor signature). - Idempotent processing — trigger the same event twice or replay a stored payload; your handler processes it once, keyed on
IdempotencyKey. - Retry handling — return a non-2xx once, confirm the event is redelivered with
"IsResent": true, and that the redelivery is deduplicated. - Unknown events tolerated — your handler acknowledges event names it does not recognize rather than erroring; new events may be added over time.
- Test-mode separation —
"IsTestMode": trueevents never touch production order state.
Updated about 9 hours ago

