Default notification
If automatic status notifications are enabled in the merchant settings, Invoicebox calls a dedicated URL from those settings as soon as an order is paid.
The call is a POST with Content-Type: application/json; the body holds an
OrderNotification object.
OrderNotification
Repeats the structure of OrderResponse. The main fields:
| Property | Required | Type | Description |
|---|---|---|---|
| id | yes | string(36) | Order identifier in Invoicebox, for example 01771534-1a57-f184-dee3-ebeb91dded75 |
| status | yes | string(50) enum | Order status, for example completed, canceled |
| merchantId | yes | string(36) | Merchant identifier, for example 01771534-1a57-f184-dee3-ebeb91dded76 |
| merchantOrderId | yes | string(36) | Order identifier in the merchant's own system, for example O-12345 |
| merchantOrderIdVisible | no | string(100) | Order number shown on the payment page; falls back to merchantOrderId, for example 111TN22-33 |
| amount | yes | float | Order total, for example 19658.45 |
| customer | yes | Customer | Buyer details |
| currencyId | yes | string(3) enum | Order currency, for example RUB, USD, EUR, GBP |
| createdAt | yes | datetime | When the order was created, for example 2026-09-14T00:00:00+00:00 |
An order counts as paid when its status is completed. Checking the signature is not enough: compare the
amount in the notification with the amount in your own system, and compare the status with the state the
order is already in. If either disagrees, refuse the request and answer with an error — otherwise a
replayed old notification or a tampered amount passes for a payment.
Response format
On success the merchant's web service returns a NotificationSuccess object; on a deliberate processing error — wrong amount, unknown order, bad signature and so on — a NotificationError.
Important
Both answers are returned with HTTP 200. HTTP 200 alone does not mean success: success is the pair
HTTP 200 + {"status":"success"} in the body. The pair HTTP 200 + {"status":"error", "code": ...}
is an expected answer too — it reports a deliberate processing error (see
NotificationErrorCode).
Any answer with a status code other than 200 — and any answer without valid JSON in the body — is read
by Invoicebox not as the code in the body but as the merchant's web service being unavailable, that
is, the same as out_of_service, no matter what code says. Such requests are retried up to 10 more
times within 24 hours.
In other words, code only means something when the status code is 200. If your service answers HTTP
400 or 401, the body is not parsed at all and the request is retried as out_of_service, even when the
body says signature_error.
NotificationSuccess
| Property | Required | Type | Description |
|---|---|---|---|
| status | yes | string(50) | On success only one value is allowed — success |
Example:
{
"status": "success"
}
NotificationError
| Property | Required | Type | Description |
|---|---|---|---|
| status | yes | string(50) enum | On a processing error only one value is allowed — error |
| code | no | string(100) enum | Error code from NotificationErrorCode; defaults to out_of_service |
| message | no | string(500) | Free-text description of the error |
Example:
{
"status": "error",
"code": "order_wrong_amount",
"message": "Order amount does not match the payment amount"
}
NotificationErrorCode
| Error code | Description |
|---|---|
out_of_service | A technical failure in the merchant's web service. Invoicebox retries the request 10 more times over the next 24 hours |
order_wrong_amount | The order amount in the merchant's system differs from the amount in the notification |
order_already_paid | The order has already been paid with another payment instrument :warning: |
order_not_found | The order is unknown to the merchant's system |
shipping_unavailable | The service cannot be rendered or the goods cannot be delivered |
full_refund_required | The same as shipping_unavailable, with a refund or payment cancellation created automatically |
signature_error | The request signature did not verify |
Important
If a notification with the same id has already been processed successfully on your side, answer
status = success again — repeated delivery is normal.
If the order was paid earlier by a notification with a different id or by another payment instrument,
answer status = error with code = order_already_paid.
Request timeout
Invoicebox waits 20 seconds for the merchant's web service to answer. Anything slower counts as a failure.
Request signature
Verify the integrity of every notification: compute the signature of the incoming body and compare it with
the X-Signature header. If they differ, answer with NotificationError and
NotificationErrorCode signature_error — with HTTP 200, like any other
NotificationError (see Response format). Answering 400, 401 or 403
instead means Invoicebox never parses code from the body and treats the call as the technical failure
out_of_service rather than signature_error.
The signature is a cryptographic transformation of the request body using the key and algorithm chosen in
the notification settings. Merchants connected today start with the hash_hmac method and sha256 —
use that on a new integration. Merchants configured earlier keep the algorithm chosen back then, usually
sha1; it still works, but switch to sha256 when you can. The algorithm and the key are both in the
merchant integration settings in your Invoicebox account.
Signature check in PHP
<?php
$xSignature = false;
foreach (getallheaders() as $header => $value) {
if (strtolower($header) == "x-signature") {
$xSignature = $value;
break;
}
}
if (!$xSignature) {
// No signature in the request.
// The status code is not set explicitly, so the default 200 applies — which is
// required here, otherwise Invoicebox will not read the code from the body.
header("Content-Type: application/json");
die('{"status":"error","code":"out_of_service"}');
}
$payload = file_get_contents("php://input");
$apiKey = ""; // The signing key from the merchant integration settings
// Use the algorithm set in the notification settings: sha256 for new merchants,
// usually sha1 for merchants configured earlier
$calcSignature = hash_hmac("sha256", $payload, $apiKey);
if ($xSignature != $calcSignature) {
// The signature does not match.
// Again HTTP 200 by default, not 400 or 401.
header("Content-Type: application/json");
die('{"status":"error","code":"signature_error"}');
}
Signature check in Python
import hashlib
import hmac
import json
# Adjust the path and the method in @app.route() to your own application.
@app.route("/invoicebox_callback", methods=['POST'])
async def invoicebox_callback():
x_signature = request.headers.get('x-signature')
if not x_signature:
# No signature in the request. The status code has to be 200, otherwise
# Invoicebox ignores the code in the body and counts the answer as a
# technical failure (out_of_service).
response = jsonify({'status': 'error', 'code': 'out_of_service'})
response.headers["Content-Type"] = "application/json"
return response, 200
# The signing key from the merchant integration settings
api_key = ""
payload = request.data
# Use the algorithm set in the notification settings: sha256 for new merchants,
# usually sha1 for merchants configured earlier
calc_signature = hmac.new(api_key.encode(), payload, hashlib.sha256).hexdigest()
if x_signature != calc_signature:
# The signature does not match. HTTP 200, not 400 or 401 — see "Response format".
response = jsonify({'status': 'error', 'code': 'signature_error'})
response.headers["Content-Type"] = "application/json"
return response, 200
Monitoring and automatic integration testing
Invoicebox monitoring tests integrations on its own.
To check that yours works, the system may send a test notification: the merchant order identifier is
empty and the Invoicebox order identifier is ffffffff-ffff-ffff-ffff-ffffffffffff.
On such a request the merchant's system should verify the signature, compare the merchant identifier with its settings and answer according to the result of those checks.