# BaaS

# Payment Routing

To connect to the service and get started, please contact Support (opens new window) or your supervising manager (opens new window).

# Sub-merchant Registration

A sub-merchant can be a legal entity or a sole proprietor.

To get started, a sub-merchant must independently create a Mandarin Personal Account through the standard registration (opens new window) process, completing steps from (1) submitting an application to (4) completing the application.

In addition to the main Personal Account registration process, bulk Personal Account creation is available through the registry. To use this scenario, please contact your supervising manager.

Once your Personal Account is approved, you can proceed to linking the created Sub-Merchant Personal Account to your Marketplace account.

# Creating a Sub-Merchant Account

To create a Sub-Merchant account, use the token, which is available in the Routing section of the Project Settings in the Sub-Merchant Personal Account.

Parameter Type Required Description
accountType string Yes Account type. For a legal entity, it is set to business.
token string Yes Legal entity account token.

The synchronous response contains the account id.

Request

POST https://secure.mandarinpay.com/api/v1/accounts/business
{

"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJtZXJjaGFudF9pZCI6MiwiaXNzIjoiMzAg0LzQsNGPIDIwMjIg0LMuIn0.unGctyIBkp4EHXw-7bFqWbbkmfhs2yCJ-jAKJGqRP_1"

}

Response if account is successfully created (200 OK)

{
"id": "01dab7d8-be9b-4f87-ba91-801731787725",
"type": "Business"
}

Response if the request is not created (400 Bad request)

{
"error": "Invalid request"
}

# Payment Acceptance (Routing)

Standard API requests for payment acceptance are used, as described in the documentation. Single-stage payment (api_payments.md#single-stage-payment), recurring payment (auto-debit) (api_payments.md#recurring-payment-auto-debit), and two-stage payment (api_payments.md#two-stage-payment) are possible (in this case, authorization remains standard, and payment distribution occurs upon completion of settlements).

In this case, the routing.destination object containing the payment distribution scheme is added to the pay request. To accept payments, the sum of the amount values ​​from the routing object must equal the top-level amount!

IMPORTANT! When using two-step payment, the routing object is added to the second request with "action": "pay"

The payer can enter card details on the payment page or the embedded Mandarin Custom Pay payment form.

Parameter Type Required Description
routing Yes An object containing routing parameters.
routing.destination string Yes An array containing payment recipients.
routing.destination. accountId string Yes Recipient account ID.
routing.destination. amount.value string Yes The amount transferred to the recipient's account. For accepting payments, the platform fee will be deducted from this amount. Separator is a period.
routing.destination.amount.currency string Yes The currency of the amount transferred to the recipient's account. Currently, it is always RUB.
routing.destination.platformFeeAmount.value string Yes The platform fee, deducted from the amount transferred to the recipient's account. Separator is a period.
routing.destination.platformFeeAmount.currency string Yes The currency of the platform fee. Currently, it is always RUB.
routing.destination.description string No Description

In the example below, 5,000 rubles are debited from the payer's card and will be credited to the following recipients:

  • 3,950 rubles to account 16f90c5e-6bc3-11eb-9439-0242ac130002, which belongs to a legal entity.
  • 50 rubles to the platform account.
  • 950 rubles to account 8ba85f01-8cc8-4161-b45d-ce6442e678ae, which belongs to another legal entity.
  • another 50 rubles to the platform account. Total: 100 rubles to the platform account.

Request one-step payment

POST https://api.mandarin.io/secure/transactions
{ 
"payment": { 
"action": "pay", 
"orderId": "your_unique_order_id", 
"amount": { 
"value": "5000.00", 
"currency": "RUB" 
}, 
"orderActualTill": "2020-02-20 12:34:56+00:00" 
}, 
"routing": { 
"destination": [{ 
"accountId": "16f90c5e-6bc3-11eb-9439-0242ac130002", 
"amount": { 
"value": "4000.00", 
"currency": "RUB" 
}, 
"platformFeeAmount": { 
"value": "50.00", 
"currency": "RUB" 
}, 
"description": "" 
}, 
{ 
"accountId": "8ba85f01-8cc8-4161-b45d-ce6442e678ae", 
"amount": { 
"value": "1000.00", 
"currency": "RUB" 
}, 
"platformFeeAmount": { 
"value": "50.00",
"currency": "RUB"
},
"description": ""
}
]
},
"customerInfo": {
"email": "user@example.com",
"phone": "+79001234567"
}
}

The response to the request matches the standard response for creating transactions.

Response if the transaction is successfully created (200 OK)

{
"id": "43913ddc000c4d3990fddbd3980c1725",
"userWebLink": "https://secure.mandarinpay.com/Pay?transaction=0eb51e74-e704-4c36-b5cb-8f0227621518",
"jsOperationId": "9874694yr87y73e7ey39ed80"
}

Response if the transaction is not created (400 Bad request)

{
"error": "Invalid request"
}

# Tokenization (Routing)

Standard tokenization requests are used for tokenization. The card token can then be used to create recurring charges, payments with a saved card in interactive mode, or payments with a saved card without entering the CVV code and without completing the checkout process. 3d-secure](api_payments.md#payment-by-saved-card-without-entering-cvv-cvc-code-and-without-passing-3d-secure)

# Payment Cancellation (Routing)

Standard API requests for refunds on previously completed payments are used, in accordance with the documentation.

To cancel a successful card debit transaction ("action": "pay"), use "action": "reversal" and the id of the previously completed transaction as target.transaction.

In this case, the routing.source object is added to the pay request, containing the refund distribution scheme between Sub-merchant accounts. To accept payments: the sum of the amount values ​​from the routing object must equal the top-level amount!

Cancellation is possible for the entire The transaction amount, as well as a partial cancellation (partial cancellation). An unlimited number of partial cancellations of a single payment transaction is allowed within the payment amount. Active participation of the payer is not required.

Parameter Required Parameter Required
routing Yes An object containing routing parameters.
routing.source string Yes An array containing payers.
routing.source.accountId string Yes The payer's account identifier.
routing.source.amount.value string Yes The amount transferred to the payer's account. Separator is a period.
routing.source.amount.currency string Yes The currency of the amount debited from the account Payer's Fee. Currently always RUB.
routing.source.platformFeeAmount. value string Yes Used when refunding the retained fee; can be 0. Separator: period.
routing.source.platformFeeAmount. currency string Yes Platform fee currency. Currently always RUB.
routing.source.description string No Description.

The synchronous response and asynchronous callback-notification may contain a wider range of parameters than in the example.

Request

POST https://secure.mandarinpay.com/api/transactions
{
"payment": {
"action": "reversal",
"orderId": "your_unique_order_id",
"price": "5000.00" 
}, 
"target": { 
"transaction": "43913ddc000c4d3990fddbd3980c1725" 
}, 
"customValues": [ 
{"name": "first parameter to save and show", "value": "p1"}, 
{"name": "second parameter to save and show", "value": "p2"} 
], 
"metadata": { 
"first_parameter_to_callback_and_not_to_show": "p1", 
"second_parameter_to_callback_and_not_to_show": "p2" 
}, 
"routing": { 
"source": [{ 
"accountId": "16f90c5e-6bc3-11eb-9439-0242ac130002", 
"amount": { 
"value": "4000.00", 
"currency": "RUB" 
}, 
"platformFeeAmount": { 
"value": "50.00", 
"currency": "RUB" 
}, 
"description": "" 
}, 
{ 
"accountId": "8ba85f01-8cc8-4161-b45d-ce6442e678ae", 
"amount": { 
"value": "1000.00", 
"currency": "RUB" 
}, 
"platformFeeAmount": { 
"value": "50.00", 
"currency": "RUB" 
}, 
"description": "" 
} 
] 
}, 
"urls": { 
"callback": "http://...", 
"return": "http://..." 
}
}

Reply if successful Transaction creation (200 OK)

{
"id": "43913ddc000c4d3990fddbd3980c1725"
}

Response if the transaction is not created (400 Bad request)

{
"error": "Invalid request"
}

# Funds Transfer (Routing)

Funds are transferred to Sub-merchants automatically the next business day after the payment date.

# Working with Virtual Accounts

# Getting Started

Before starting the integration, please contact support to obtain the following information:

  • client_id
  • client_secret
  • access to the Sandbox environment
  • (optional) webhook URL for receiving wallet status notifications

This information is used to obtain the **access_token` and make API requests.


# Environments

Two environments are available:

Environment Base URL
Sandbox https://sandbox-payment-tokens.mandarin.io
Production https://payment-tokens.mandarin.io

Sandbox is used for integration testing.


# Request Authentication

OAuth2 Bearer Tokens are used to access the API.

# Token Request

Production Entry Point:

POST https://accounts.mandarinbank.com/oauth/token/

Includes mandatory form-data parameters that are passed in the request body (the application.client_id and application.client_secret parameters are constant across the application and are stored on the client side).

Parameter Description Example
grant_type Authorization grant (always equal to client_credentials). client_credentials
client_id Client ID (equal to the application.client_id value provided by Mandarin). VvPtlhcyldKtkuoUWY42 pErdrj4er2AwFoBWrn8n
client_secret The client secret password (equal to the application.client_secret value provided by Mandarin). uQfOIMsltZYL8x3XMqUGP5 iFM59PyFnKlN0UmD3Ihre2 Ry3AGazUAv5jPdUI4dBJqV 0Of6b9GFvWvzGahYnq2aVV xkxn9n4qWF57FP0C01Kp6l EtajhfYv3UZ2f4pAZ7
scope The requested scopes (access areas):
e.g., transactions.read - "Read transactions".
Separator - space.
You can request any list of scopes, and the response will return the list of requested scopes that can be provided for this application.
payment-tokens:tokens.write payment-tokens:tokens.read payment-tokens:otp.write

Example request:

curl --location 'https://accounts.mandarinbank.com/oauth/token/' \
--form 'grant_type=client_credentials' \
--form 'client_id=YOUR_CLIENT_ID' \
--form 'client_secret=YOUR_CLIENT_SECRET' \
--form 'scope=REQUESTED_SCOPES'

The scope parameter must specify the permissions for the API methods being called.

Example:

payment-tokens:tokens.write payment-tokens:tokens.read payment-tokens:otp.write

Permissions are passed separated by spaces, without additional characters.

The received token must be passed in the header of all API requests:

Authorization: Bearer {access_token}

# Creating a virtual account (ESP)

# Entry points

Sandbox:

POST https://sandbox-payment-tokens.mandarin.io/api/v1/tokens/generate

Production:

POST https://payment-tokens.mandarin.io/api/v1/tokens/generate

Required scope:

payment-tokens:tokens.write

Request

curl --location 'https://sandbox-payment-tokens.mandarin.io/api/v1/tokens/generate' \
--header 'Mid: 123' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer {access_token}' \
--data-raw '{ 
"first_name": "Nikolai", 
"last_name": "Nikolaev", 
"middle_name": "Nikolaevich", 
"birth_date": "1993-12-22", 
"citizenship": "RU", 
"registration_address": "Moscow, Petrovka st., 2, apt. 1", 
"living_address": "Moscow, Petrovka st., 2, apt. 1", 
"document": { 
"document_type": "Passport", 
"serial": "1233", 
"number": "123458", 
"issue_date": "2002-04-24T15:23:14.969Z", 
"birth_place": "Moscow", 
"issuer": "Fili Davydkovo District Department of Internal Affairs", 
"issue_code": "404-004", 
"expiration_date": null 
}, 
"inn": "777777777777", 
"snils": "140-120-150 35", 
"phones": [ 
{ 
"phone": "79017636353", 
"type": "Personal" 
} 
], 
"email": "ivanoff@mail.ru", 
"fax": "string", 
"phone_check": true, 
"terms_agreement": true, 
"is_public_official_person": false,
"presence_of_beneficiary": false,
"beneficiary_information": false,
"exist_fatf_government_bills": false,
"affiliation_with_foreign_taxpayers": false
}'

Response if the create request is successful (200 OK)

{
"id": "f289271a-914d-4987-88c4-d7cc66482c75",
"status": "Processing",
"created_at": "2026-03-16T10:00:36.0018804Z",
"processed_at": null,
"finished_at": null
}

The id field is used as the payment_token_id in subsequent requests.


# Send to OTP

Sends an SMS confirmation code.

Entry point:

POST /payment-tokens/api/v1/otp/{payment_token_id}/send

Required scope:

payment-tokens:otp.write

Response if the SMS code was successfully sent (200 OK)

{
"to": "79017636353",
"ttl": 1773659797,
"status": "pending",
"retries": 10
}

# OTP verification

Confirms the SMS code.

Entry point:

POST /payment-tokens/api/v1/otp/{payment_token_id}/verify

Required scope:

payment-tokens:otp.write

Request:

{
"code": "0153"
}

Response if the code is successfully verified (200 OK)

{
"verified": true,
"retries": 9
}

# Checking the ESP status

Request:

GET /payment-tokens/api/v1/tokens/{payment_token_id}/status

Required scope:

payment-tokens:tokens.read

Response if the creation request is successfully sent (200 OK)

{
"id": "06dca2f1-4e1c-44e7-8848-3e9e3adba875",
"status": "Success",
"created_at": "2025-11-21T10:33:27.021823Z",
"processed_at": "2025-11-21T10:33:32.431478Z",
"finished_at": "2025-11-21T10:33:33.022295Z"
}

# Webhooks

You can set up a webhook to receive notifications about your wallet status.

The webhook is sent using the POST method.

Payload:

{
"event_id": "17c5fdaf-3570-4d77-8966-6ae4505c2454",
"payment_token_id": "98a4a683-305e-4664-8d96-5f51d9e668f6",
"status": "Success",
"created_at": "2026-03-04T14:44:57.079171Z"
}

# ESP Statuses

Status Description
Processing Wallet is being created
Success Wallet is activated
Failed Creation error
Blocked Wallet is blocked
Suspended Wallet suspended

# Data for test requests

# Successful wallet creation

first_name: Successful
last_name: Success
middle_name: Successful

# Wallet creation error

first_name: Unsuccessful
last_name: Unsuccessful
middle_name: Unsuccessful

# Integration process

  1. Get access token
  2. Create ESP (generate)
  3. Send SMS code (send)
  4. User enters code
  5. Confirm code (verify)
  6. Get ESP status (status) or via webhook

# Balance Receive ESP

Request

curl --location 'https://payment-tokens.mandarin.io/api/v1/tokens/{{payment_token_id}}/wallet/balance' \
--header 'Authorization: Bearer Token' \
--header 'Mid: 3019'

Response if balance is successfully received (200 OK)

{
"wallet_id": "fsdfrg3amt",
"balance": 1500075,
"currency": "RUB"
}

# Payment Transactions

All payment transactions are processed through a single endpoint.

POST https://secure.mandarinpay.com/api/transactions

The operation is asynchronous. The final status is transmitted in a callback notification.

# Internal Operations

Depositing funds to an ESP (business → wallet)

Request

curl --location 'https://secure.mandarinpay.com/api/transactions' \
--header 'Content-Type: application/json' \
--header 'Mid: 777' \
--header 'Authorization: Bearer Token' \
--data-raw '{
"payment": {
"action": "payout",
"orderId": "your_unique_order_id",
"price": "10.00",
"orderActualTill": "2026-02-20 12:34:56+00:00"
},
"target": {
"paymentToken": "payment_token_id"
}, 
"customerInfo": { 
"email": "user@example.com", 
"phone": "+79001234567" 
}, 
"customValues": [ 
{ "name": "first parameter to save and show", "value": "p1" }, 
{ "name": "second parameter to save and show", "value": "p2" } 
], 
"metadata": { 
"first_parameter_to_callback_and_not_to_show": "p1", 
"second_parameter_to_callback_and_not_to_show": "p2" 
}, 
"urls": { 
"callback": "http://...", 
"return": "http://..." 
}
}'

Response in case of successful transaction creation (200 OK)

{ 
"id": "43913ddc000c4d3990fddbd3980c1725",
"userWebLink": "https://secure.mandarinpay.com/Pay?transaction=0eb51e74-e704-4c36-b5cb-8f0227621518",
"jsOperationId": "9874694yr87y73e7ey39ed80"
}

Response if the transaction is not created (400 Bad Request)

{
"error": "Invalid request"
}

Withdrawing funds from the ESP to a business account (wallet → business)

Request

curl --location 'https://secure.mandarinpay.com/api/transactions'\
--header 'Content-Type: application/json' \
--header 'Mid: 777' \
--header 'Authorization: Bearer Token' \
--data-raw '{ 
"payment": { 
"action": "pay", 
"orderId": "your_unique_orde1f313fr_id", 
"price": "10.00", 
"orderActualTill": "2026-02-20 12:34:56+00:00" 
}, 
"target": { 
"paymentToken": "payment_token_id" 
}, 
"customerInfo": { 
"email": "user@example.com", 
"phone": "+79001234567" 
}, 
"customValues": [ 
{ "name": "first parameter to save and show", "value": "p1" }, 
{ "name": "second parameter to save and show", "value": "p2" } 
], 
"metadata": { 
"first_parameter_to_callback_and_not_to_show": "p1", 
"second_parameter_to_callback_and_not_to_show": "p2" 
}, 
"urls": { 
"callback": "http://...", 
"return": "http://..." 
}
}'

Response in case of successful transaction creation (200 OK)

{
"id": "9b3c8a1f7e52d4096b0c2f8d3a5e17b4",
"userWebLink": "https://secure.mandarinpay.com/Pay?transaction=f2a85b1c-3e9d-4871-a0b4-6c83d9f1e502",
"jsOperationId": "p3j8m267t1n9f4k5d0q8b3v6"
}

Response if the transaction is not created (400 Bad request)

{
"error": "Invalid request"
}

# External transactions (withdrawals from ESP)

To a card by card number (wallet → knownCardNumber)

Request

curl --location 'https://secure.mandarinpay.com/api/transactions' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer Token' \
--data-raw '{ 
"payment": { 
"action": "payout", 
"orderId": "1756292752", 
"price": "100" 
}, 
"customerInfo": { 
"email": "noreply@example.ru", 
"phone": "+72244861047" 
}, 
"source": { 
"paymentToken": "63eed817-c56e-4aac-ad38-15113ed13744" 
}, 
"destination": {
"knownCardNumber": "2202202244861047"
}
}'

Response if the transaction was successfully created (200 OK)

{
"id": "e7a2f1b409d83c6a25e0b8d14c9f3a72",
"userWebLink": "https://secure.mandarinpay.com/Pay?transaction=7d14c9a3-0b8e-42f6-95c1-e3a6f0d8b247",
"jsOperationId": "k5h8293xq4m7p1tg6r0n8s2v3"
}

Response if the transaction was not created (400 Bad request)

{
"error": "Invalid request"
}

To a bound card (wallet → bound card)

Request

curl --location 'https://secure.mandarinpay.com/api/transactions' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer Token' \
--data-raw '{ 
"payment": { 
"action": "payout", 
"orderId": "1756292807", 
"price": "100" 
}, 
"customerInfo": { 
"email": "noreply@example.ru", 
"phone": "+72244861047" 
}, 
"source": { 
"paymentToken": "63eed817-c56e-4aac-ad38-15113ed13744"
},
"destination": {
"card": "63eed817-c56e-4aac-ad38-15113ed13722"
}
}'

Response if the transaction was successfully created (200 OK)

{
"id": "a7f4c2098e3b15d60f8a2e1b7c390d4f",
"userWebLink": "https://secure.mandarinpay.com/Pay?transaction=1d8f3b9a-6e05-4c12-9a7d-0c4a8b5f3e12",
"jsOperationId": "j384m76t9k5f1d2w0n8q7x3r5"
}

Response if the transaction is not created (400 Bad request)

{ 
"error": "Invalid request"
}

On SBP (1-step)

curl --location 'https://secure.mandarinpay.com/api/transactions' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer Token' \
--data-raw '{ 
"payment": { 
"action": "payout", 
"orderId": "1756292895", 
"price": "100" 
}, 
"customerInfo": { 
"phone": "+7926087626",
"email": "test@example.ru",
"firstName": "Ivan",
"middleName": "Valerievich",
"lastName": "Ivanov"
},
"source": {
"paymentToken": "63eed817-c56e-4aac-ad38-15113ed13744"
},
"destination": {
"sbp": {
"bankId": "100000000008",
"bankBic": "044525593"
}
}
}'

Response if the transaction is successfully created (200 OK)

{
"id": "43913ddc000c4d3990fddbd3980c1725",
"userWebLink": "https://secure.mandarinpay.com/Pay?transaction=0eb51e74-e704-4c36-b5cb-8f0227621518",
"jsOperationId": "9874694yr87y73e7ey39ed80"
}

Response if the transaction is not created (400 Bad Request)

{
"error": "Invalid request"
}

On the FPS with confirmation (2-step)

Creating a payout

curl --location 'https://secure.mandarinpay.com/api/transactions' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer Token' \
--data-raw '{ 
"payment": { 
"action": "payout", 
"orderId": "1756291493", 
"price": "100" 
}, 
"customerInfo": { 
"phone": "+7926087626", 
"email": "test@example.ru", 
"firstName": "Ivan", 
"middleName": "Valerievich", 
"lastName": "Ivanov" 
}, 
"source": { 
"paymentToken": "63eed817-c56e-4aac-ad38-15113ed13744" 
}, 
"destination": { 
"sbp": { 
"bankId": "100000000008", 
"bankBic": "044525593" 
}
}
}'

Response if the transaction was successfully created (200 OK)

{
"id": "e8f2a1b90c7d34f562a8e0b9d1c7f3a5",
"userWebLink": "https://secure.mandarinpay.com/Pay?transaction=9d4c7a1f-3b28-4e65-8f09-2a5b6c0d7e31",
"jsOperationId": "z5q8n2r7k1m4f9t0p3w6b8x2v"
}

Response if the transaction was not created (400 Bad request)

{
"error": "Invalid request"
}

Receiving Full Name

POST https://secure.mandarinpay.com/api/sbp/{id}/status

Payment Confirmation

POST https://secure.mandarinpay.com/api/sbp/{id}/confirm

Payment Rejection

POST https://secure.mandarinpay.com/api/sbp/{id}/reject

# Transit Transactions

Payout to Card with Transit via ESP

Request

curl --location 'https://secure.mandarinpay.com/api/transactions'\
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer Token' \
--data-raw '{ 
"payment": { 
"action": "payout", 
"orderId": "1756291886", 
"price": "100" 
}, 
"customerInfo": { 
"email": "noreply@example.ru", 
"phone": "+72244861047" 
}, 
"transit": { 
"paymentToken": "63eed817-c56e-4aac-ad38-15113ed13744" 
}, 
"target": { 
"card": "63eed817-c56e-4aac-ad38-15113ed13722"
}
}'

Response if the transaction was successfully created (200 OK)

{
"id": "8c3f9a2d1e7b0456f0c8d2e5a1b7f3d9",
"userWebLink": "https://secure.mandarinpay.com/Pay?transaction=0eb51e74-e704-4c36-b5cb-8f0227621518",
"jsOperationId": "9874694yr87y73e7ey39ed80"
}

Response if the transaction was not created (400 Bad request)

{
"error": "Invalid request"
}

# Payment Transactions

Topping up an ESP with a linked card

Request

curl --location 'https://secure.mandarinpay.com/api/transactions' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer Token' \
--data-raw '{
"payment": {
"action": "pay",
"orderId": "1756293713",
"price": "1"
},
"customerInfo": {
"email": "noreply@example.ru",
"phone": "+72244861047"
},
"source": {
"card": "63eed817-c56e-4aac-ad38-15113ed13722"
},
"destination": {
"paymentToken": "63eed817-c56e-4aac-ad38-15113ed13721"
}
}'

Response if the transaction is successfully created (200 OK)

{
"id": "43913ddc000c4d3990fddbd3980c1725",
"userWebLink": "https://secure.mandarinpay.com/Pay?transaction=0eb51e74-e704-4c36-b5cb-8f0227621518",
"jsOperationId": "9874694yr87y73e7ey39ed80"
}

Response if the transaction is not created (400 Bad request)

{
"error": "Invalid request"
}

Important notes for merchants

All scenarios are implemented by a sequence of calls to existing API methods. There are no separate endpoints for "payout," "acceptance," "refinancing," or "funds distribution." Scenarios are generated by platform-side logic. The same API paths are used for sandbox and production. The differences lie in the service domains and the merchant_id and secret values. All operations are asynchronous and require callback notifications.