What a notification is

A notification is an outbound HTTP call that we make to an endpoint you control, telling you that something on the platform changed. It is not a response to a request you made. It arrives on its own, potentially minutes or hours after the original request, because the underlying change is driven by a bank, a payout provider or an operator rather than by your API call.

Every notification is sent as an HTTP POST with a application/x-www-form-urlencoded body. Your endpoint must accept POST, and should return HTTP 200 as soon as it has stored the message.

Notifications are signed. The signature lets you prove that the body was produced by us and was not modified in transit, which matters because the endpoint is a public URL that anyone can post to. Always verify the signature before acting on a notification.

The two kinds of notification

Which endpoint we call, and what the body looks like, depends on how the notification was configured.

  Per-request notification Program notification
Used for Payout (wire) status changes Account, wallet and balance events
Endpoint The statusNotificationURL you send on the original request. Different requests can point at different URLs. One notification endpoint configured on your program. Every event of every type goes to the same URL.
Body A fixed set of named fields, documented per event. Composed from a template configured on your program, so the field set is whatever that template defines.
Signed over The field values only. The entire body.

The difference in the last row is the one that catches people out, so it is worth reading the next two sections carefully rather than reusing one verification routine for both.

The signature

Both kinds of notification use the same algorithm and the same secret. Only the input string differs.

Algorithm
signature = urlencode( base64( sha256( DATA + HashKey ) ) )

HashKey is the hash key of your program. It is a shared secret: it is never transmitted in the notification, it is only appended to the data before hashing. If you do not know your program hash key, ask your account manager. Note that this is a different key from the one used to sign requests you send to us.

The SHA-256 must be taken over the raw binary digest, then base64 encoded, then URL encoded. Hashing to a hex string first and base64ing that is the single most common implementation mistake, and it produces a value that never matches.

Per-request notifications: DATA is the values, concatenated

DATA is the value of every field in the notification, in the documented order, concatenated together. Field names are not included. There is no separator between the values, and no & or = characters. The values are taken in their URL-encoded form, exactly as they appear on the wire, so do not decode them before hashing. The signature field itself is excluded, as are any fields documented as excluded for that event.

Example - payout notification
DATA = wireId + orderID + accountId + beneficiaryId + payoutAccountId + wireStatus
     + transferAmount + transferCurrency + paymentScheme
     + payoutAccountName + payoutAccountNickname + payoutAccountFirst
     + payoutAccountLast + payoutAccountCompanyName
     + beneficiaryName + beneficiaryNickname + beneficiaryFirst
     + beneficiaryLast + beneficiaryCompanyName

See Payout Notification for the full field list and an example payload.

Program notifications: DATA is the whole body

DATA is the complete request body exactly as received, with the trailing &signature=... parameter removed. Field names, = and & separators are all included, because the body is template-defined and has no fixed field list to concatenate. Do not parse, reorder, decode or re-encode the body before hashing: take the raw bytes, cut off the signature parameter, and hash what is left.

Example - program notification
Received body:
event=balance_request_timed_out&request_id=84213&result_code=889&amount=250.00&signature=Ax7v...%3D

DATA = event=balance_request_timed_out&request_id=84213&result_code=889&amount=250.00

The signature is always the last parameter in the body, so cutting at the last occurrence of &signature= is the reliable way to split it.

Verifying
PHP - program notification
$raw = file_get_contents('php://input');
$pos = strrpos($raw, '&signature=');
$data = substr($raw, 0, $pos);
$sent = substr($raw, $pos + strlen('&signature='));

$calc = urlencode(base64_encode(hash('sha256', $data . $hashKey, true)));

if (!hash_equals($calc, $sent)) {
    http_response_code(400);
    exit;
}
C# - program notification
var pos = raw.LastIndexOf("&signature=", StringComparison.Ordinal);
var data = raw.Substring(0, pos);
var sent = raw.Substring(pos + "&signature=".Length);

string calc;
using (var sha = System.Security.Cryptography.SHA256.Create())
{
    var hash = sha.ComputeHash(System.Text.Encoding.UTF8.GetBytes(data + hashKey));
    calc = System.Net.WebUtility.UrlEncode(Convert.ToBase64String(hash));
}

if (!string.Equals(calc, sent, StringComparison.Ordinal)) return BadRequest();

For a per-request notification the only change is how $data is built: concatenate the documented values in order instead of using the raw body.

Building a reliable receiver
Return 200 quickly Store the notification and return 200 straight away. Do your processing afterwards. A response we do not read as 200 is treated as a failed delivery.
There is no automatic retry If your endpoint is down, times out or returns a non-200 status, the notification is not resent on a schedule. It is logged on our side and can be resent manually on request. Treat notifications as a fast path, not as your only source of truth, and reconcile periodically using the status and search services.
Be idempotent The same event can reach you more than once, for example after a manual resend. Key your processing on the identifier in the body and ignore anything you have already applied.
Do not infer state from arrival order Notifications are dispatched independently and can arrive out of order. Use the status and timestamp in the body, not the order of receipt.
Reject anything that fails verification Your endpoint is publicly reachable. Anything that does not verify against your program hash key should be discarded and logged, not processed.
Keep the URL simple Use a plain HTTPS URL with no query string. Extra parameters on the notification URL are not guaranteed to survive delivery.