> ## Documentation Index
> Fetch the complete documentation index at: https://docs.m4psp.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Interaction format

### Issued invoice notice after payment or refund

After changing the payment status or returning to one of the final ones, the M4  system sends a notification to the shop to the URL specified in the shop settings or in the request body (if specified in the description of the request parameters).

> **Warning:** To successfully receive server-side notifications, please make sure that your server receiving HTTP requests from us is configured according to the current security standards described on [https://cheatsheetseries.owasp.org/cheatsheets/Transport\_Layer\_Protection\_Cheat\_Sheet.html](https://cheatsheetseries.owasp.org/cheatsheets/Transport_Layer_Protection_Cheat_Sheet.html)

Pay special attention to the TLS ciphers you support.

Notifications for payments and refunds are delivered to one callback URL. You can distinguish notifications by the value of the GET parameter named `type` in the URL from which you received the notification. Possible values of `type`:

* `invoice` - payment notification
* `refund` - refund notification

Server notifications are sent from designated IP addresses available upon request from the technical support.

HTTP protocol is used to send the notification:

* Method: `POST`
* Format: `Content-Type: application/json`

> **Success:** If necessary, the support service can enable an alternative notification format: `Content-Type: application/x-www-form-urlencoded`, in this case, all the transmitted parameters will have a string representation.

> **Attention:** It is **necessary** to check the values from the notification with the values in the invoice, and **to check** signature in the notification.

The signature is generated using the same algorithm as when issuing the invoice.

All the not-null and non-empty parameters present in the notification are involved in the signature validation, including zero values. At the same time, `null` values and empty strings are not involved in signature validation and are excluded.

Consider building your system using forward compatibility strategy to be ready to handle new parameters in the server notifications without need to upgrade your own system.

### Expected response

If the notice is received and processed correctly, it is necessary to return the status `200` and the message body `OK` (`text/plain`) to the `HTTP` response. Otherwise, notices will be sent again, with an increasing interval, including only 25 attempts, the last one at the end of the day.

## Payment notification example

[Invoice callback schema](/invoice-callback-schema)

## Refund notification example

[Refund callback schema](/refund-callback-schema)

## Example of Python code to verify the signature of a request

The algorithm implemented below on the example of a server payment notification is the same for a server refund notification.

```python theme={null}
# Resulting notification string (in format application/json)
request_body_json = """
    {
      "shop_id": 100,
      "shop_order_id": "The store's order ID",
      "description": "Your payment description",
      "shop_amount": 95.00,
      "shop_refund": 92.75,
      "shop_currency": 840,
      "payment_id": 1234,
      "client_price": 97.55,
      "ps_currency": "840",
      "payway": "some_payway_name",
      "ps_data": "{\"ps_payer_account\": \"525634XXXXXX7704\"}",
      "created": "2026-06-01T15:38:07",
      "updated": "2026-06-01T15:38:07",
      "status": "success",
      "addons": "{\"comment\": \"Notification text\"}",
      "sign": "1d51960a52a6c7e8cf957d1a22ad2b3263603bace013facc335dd1429ecb4e97"
}"""

import json
parsed_request = json.loads(request_body_json)

# Resulting notification string (in format application/x-www-form-urlencoded)
if content_type == 'application/x-www-form-urlencoded':
    request_body_form_urlencoded = (
        'shop_id=100&shop_order_id=The+store%27s+order+ID&description=Your+payment+description&sh'
        'op_amount=95.0&shop_refund=92.75&shop_currency=840&payment_id=1234&client_price=97.55&ps'
        '_currency=840&payway=some_payway_name&ps_data=%7B%22ps_payer_account%22%3A+%22525634XXXX'
        'XX7704%22%7D&created=2026-06-01T15%3A38%3A07&updated=2026-06-01T15%3A38%3A07&status=succ'
        'ess&addons=%7B%22comment%22%3A+%22Notification+text%22%7D&sign=1d51960a52a6c7e8cf957d1a2'
        '2ad2b3263603bace013facc335dd1429ecb4e97'
    )
    from urllib.parse import parse_qsl
    parsed_request = dict(parse_qsl(request_body_form_urlencoded))

print(parsed_request)
# {
#     'shop_id': 100,
#     'shop_order_id': 'The store's order ID',
#     'description': 'Your payment description',
#     'shop_amount': 95.00,
#     'shop_refund': 92.75,
#     'shop_currency': 840,
#     'payment_id': '1234',
#     'client_price': 97.55,
#     'ps_currency': 840,
#     'payway': 'some_payway_name',
#     'ps_data': '{\"ps_payer_account\": \"525634XXXXXX7704\',
#     'created': '2026-06-01T15:38:07',
#     'updated': '2026-06-01T15:38:07',
#     'status': 'success',
#     'addons': '{\"comment\": \"Notification text\"}',
#     'sign': '1d51960a52a6c7e8cf957d1a22ad2b3263603bace013facc335dd1429ecb4e97',
# }

# Let's get a list of sorted keys for signature generation
keys = sorted(parsed_request)

# Let's remove the signed key from them.
keys.remove('sign')

print(keys)
# This is how the resulting list will look like
#     ['addons',
#      'client_price',
#      'created',
#      'description',
#      'payment_id',
#      'payway',
#      'ps_currency',
#      'ps_data',
#      'shop_amount',
#      'shop_currency',
#      'shop_id',
#      'shop_order_id',
#      'shop_refund',
#      'status',
#      'updated']

# Let's compose a line using the sorted key values obtained in the previous step
values_to_sign = []
for k in keys:
    if parsed_request[k] != '' and parsed_request[k] is not None:
        values_to_sign.append(str(parsed_request[k]))

# Let's form the final signature and check its value with the value received in the response
string_to_sign = ':'.join(values_to_sign) + '<secret_key_here>'
import hashlib
sign = hashlib.sha256(string_to_sign.encode()).hexdigest()
assert sign == parsed_request['sign']
```
