Skip to content

Instantly share code, notes, and snippets.

@Sominemo
Last active June 19, 2024 13:04
Show Gist options
  • Save Sominemo/64845669d6326f2f73d356f025656bdb to your computer and use it in GitHub Desktop.
Save Sominemo/64845669d6326f2f73d356f025656bdb to your computer and use it in GitHub Desktop.
Mono Corp API Proxy

Mono Corp API Proxy Protocol 1.3

This document describes the protocol of Mono Corp API Proxy (MCAP). This protocol is created for Mono PWA project.

Entities

  • Root: describes the publicly available URL path, which contains methods as subfolders;

  • Method: Named public command, the way to interact with MCAP for different actions. A simulated subfolder of Root;

  • Secret Proof: A password, that elevates access rights for the request;

  • Public Method: Generally available method, that is intended to be called by the client app;

  • Service Method: A method, that must not be called by the client app, requires Secret Proof;

  • Response: JSON marshaled output of Method with application/json Content-Type and 200 OK response code;

  • Error: A Response with an error key instead of the expected output. The key contains error description -> {"error": "description"};

  • Long-Polling: Keeping an HTTP request alive without outputting the response body until a trigger;

  • %METHOD% /%resource% Monobank Request: An HTTP %METHOD% (GET|POST|...) request to https://api.monobank.ua/%resource% -> POST /personal/auth/request Monobank Request. See Sending requests to Monobank;

  • Roll-In Token: A random string, which is paired with a Monobank Token Request ID;

  • Request Token: A random string, which is paired with a Monobank token and a Roll-In token;

  • Private Key: An OpenSSL generated key, which was passed to Monobank team while Corporative API set up;

  • Public Key: A public key, which was extracted from the Private key;

  • Key-ID: Marshaled Public key representation, hashed with sha1 (in hex). Received from Monobank team;

  • <MCAP %name%>: Mono Corp API Proxy described data structure

  • Webhook Endpoint: Service method, that accepts update requests from Monobank;

  • Webhook Request: A request with application/json input, that is being accepted by registered Webhook Endpoint;

  • Push Server: An endpoint, that accepts Request Token, has a Webhook Endpoint, can list subscriptions and send the data using Web Push API;

  • Push API Private Key: Secret self-generated VAPID key;

  • Push API Public Key: A public pair to Push API Private Key;

  • Push API Subscription: Endpoint, auth and secret which are being used to send a push with Web Push API;

  • Notification Channel: A unit, that describes a category of acceptable pushes via id and type.

Public Methods

The methods are placed in the auth lifecycle order:

Method Server action
check-proto Describes itself by telling name, author, protocol version, homepage, optionally UI message, Push Server
roll-in Creates a roll-in token
exchange-token Checks if there are available Monobank user tokens for current roll-in token, and if so, creates a pair request token and gives it
request Sends requests to Monobank

Every method must always return Access-Control-Allow-Origin: * header

Push Server Public Methods

These methods belong to Push Server (usually at /push/ location):

Method Server action
list List available subscriptions
subscribe Activate subscription and update Push API data
unsubscribe Deactivate subscription

Every method must always return Access-Control-Allow-Origin: * header

Push Server Service Methods

These methods must not be called from the app itself and require an additional secret proof:

Method Server action
broadcast Broadcast user notifications for specified channel

Push Server Methods Notice

  1. In this documentation, a Push Server method has a name, that starts with push/
  2. In reality, every request URL looks like PushServer/%ingredient%/%method-name%. For example, if your Push Server is https://example.com/push, the %ingredient% is passwd and the %method-name% is list, the Request URL will look like https://example.com/push/passwd/list

MCAP Types

Text

  • Plain text:
{
  "mode": "text",
  // The text that will be displayed
  "value": "My text"
}
  • Value from localization files:
{
  "mode": "local"
  // The ID of localization string
  "value": "@push/i/news/sign"
}
  • Account description, e.g. **2817 USD Black
{
  "mode": "statement",
  // Mono account ID
  "value": "XcG_Ynrm5eUbDrPDt2MhhQ"
}

Action

  • Open a URL:
{
   // Defines the URL action
  "act": "url",
   // The url to open
  "url": "https://example.com/",
   // Allows to open new tabs. Default - false
  "openNew": false,
   // Allows to refocus the client tab to open the url. Default - false
  "refocus": false

}
  • Broadcast a JavaScript message:
{
   // Defines the message broadcast action
  "act": "msg",
   // JSON marshaled data to broadcast
  "data": "{...}",
  // Allow to broadcast the message to all available windows. Default - false
  "all": false,
   // Denies to open new tabs. Default - false
  "denyNewWindow": false,

}

Push

  • Custom multilang push:
{
    "act": "custom-push",
    "isMultilang": "true",
    "push": {
        "ru": {
            <MCAP Notification>
        },
        "uk": {
            <MCAP Notification>
        }, 
        ...
    }
}
  • Custom single language push:
{
    "act": "custom-push",
    "push": {
        <MCAP Notification>
    }
}
  • Transaction push
{
    "act": "statement-item",
    "item": {<StatementItem>},
    "account": {<Account>}
}

Notification Options

Notification

{
     // Notification title
    "title": "String",
    ...<MCAP Notification Options>,
     // Use included into client badge
    "libraryBadge": "m" || "news" || "payIn" || "payOut" || "card",
     // Describes action for different onclick payloads.
    "actionDescriptor": {
      // Empty payload serves simple notification click
      "": <MCAP Action>
    }
}

Sending requests to Monobank

Note

Monobank API is sensible to HTTP method

Root

The Root for Monobank API is https://api.monobank.ua

Headers

Header Content Description
X-Time Unix timestamp php time()
X-Sign Sign See Signing the request
X-Key-Id Key-ID See Entities
X-Token Monobank token If present

Signing the request

Not to be confused with encryption

1. The ingredients

The signed string contains 3 ingredients:

  1. Timestamp from X-Time
  2. Monobank token usually, can be something else. Referred as 2nd Sign Ingredient
  3. Requested Monobank /%resource%

If it's an auth request to /personal/auth/request, token permissions from X-Permissions are being used instead of 2nd ingredient

2. Make the string to sign

The string contains all the ingredients concatenated together ($ing1.$ing2.$ing3 for PHP, ${ing1}${ing2}${ing3} or ing1+ing2+ing3 for JavaScript)

3. Create the sign

  1. Make a sign of the string, that corresponds to the OpenSSL signing
Property Value
Algorithm SHA256
Key Private Key
  1. Encode the sign to Base64

Examples

PHP
$key = openssl_get_privatekey("file://private.key", "");
    $str = $time.$t.$url;
    openssl_sign($str, $sig, $key, OPENSSL_ALGO_SHA256);
    openssl_free_key($key);
    return base64_encode($sig);
Python
import base64
import ecdsa
...
data = (timestamp + permissions + url).encode('utf-8')
sign = PrivateSigningKey.sign(data, hashfunc=hashlib.sha256)
signB64 = base64.b64encode(sign)
Go
See https://github.com/shal/mono

Set up a Webhook Endpoint

Make sure to keep the Endpoint path secret
  • Input data format:
{
  "type": "StatementItem",
  "data": {
    "account": Account.id,
    "statementItem": <StatementItem>
  }
}

Register your webhook

POST /personal/corp/webhook Monobank Request

  • 2nd Sign Ingredient is empty
  • Request body is {"webHookUrl": "%webhook%"}, where %webhook% is your Webhook Endpoint URL
  • Response is empty

GET /personal/corp/settings Monobank Request

  • 2nd Sign Ingredient is empty

check-proto

[public method]

Server action

Describes itself by telling name, author, protocol version, homepage and optionally setting a UI message

Response

All fields are required unless otherwise specified

{
    // Describes used MCAP protocol
  "proto": {
    "version": 1,
    "patch": 3
  },
    // Describes the implementation of protocol
  "implementation": {
    "name": "PHP Mono Corp API Proxy",
    "author": "Sominemo",
    "homepage": "https://github.com/Sominemo/Mono-Corp-API-Proxy-PHP"
  },
    // Describes the state of the running instance of this implementation
  "server": {
      // Tells the client to display a message to the user, optional
    "message": {
       // The text to display
      "text": "content",
       // Adds clickable URL at the end of text, optional
      "link": "https://example.com"
    },
      // Describes a Push Server if available, optional
    "push": {
        // Push Server endpoint
      "api": "https://example.com/push",
        // Push API public key
      "cert": "QktvWDBndm44VWFCdjNnQVhDM0x4Q0lfblJQ...",
        // Push Server display name
      "name": "Sominemo Push Server (example.com)",

    }
  }
}

roll-in

[public method] Creates an auth request in Monobank, generates a paired roll-in token and returns both

User interacts with Monobank interface and confirms the auth request, then Monobank sends a request to a Webhook. More details in Getting the Monobank token.

Server actions

  1. POST /personal/auth/request Monobank request Headers(X-Callback: %webhook%; X-Permissions: %permissions%)
  • %webhook% is a public http resource to receive the resulting request from Monobank
  • %permissions%:
List of rights that the service wants to receive from the client (1 letter per 1 permission). List of possible rights:

s - statement (includes balance and statement itself)
p - Personal information (name and surname)

  1. Get Monobank Token Request ID %requestId% from the response {"tokenRequestId": "***"} and Auth URL %url% {"acceptUrl": "***"}
  2. Error, if the request went wrong
  3. Generate Roll-In token %token% and a random %proof% string for it, pair the token with Monobank Token Request ID
  4. Generate QR code from Auth URL (250px, e.g. using Google Charts API)
  5. Encode the QR to Base64 %qr%

Response

{
    "token": %token%,
    "requestId": %requestId%,
    "url": %url%,
    "qr": %qr%
}

Getting the Monobank token

  1. Set up an endpoint, that will receive a %proof% and %token% as GET parameters or in-uri (e.g. /webhook/%token%/%proof% or /webhook?token=%token%&proof=%proof%)
  2. Receive the Monobank User Token in X-Request-Id (can be also x-request-id) incoming header
  3. Check if %proof% and %token% from GET correspond each other, else Error
  4. Generate a Request Token, pair Roll-In and the Monobank User tokens with it
  5. If you are not going to support multiple devices Sign-In, skip to 7
  6. Make GET /client-info Monobank request and retrieve clientId from it.
  7. Update Monobank Token for every Request Token with such clientId. Link the clientId with current Request Token.
  8. Response 200 OK

exchange-token

[public method] Long-polling until received roll-in token won't get a corresponding request token or the request will timeout. Checks if there are available Monobank user tokens for current roll-in token, and if so, creates a pair request token and gives it

Server actions

  1. Receive an existing roll-in token from user
  2. Error, if the token does not exist
  3. Check if Request Token that corresponds to the Roll-In Token already exists
  4. If so, skip to authed
  5. Start Long-Polling until token receive. On trigger skip to authed
  6. If current Long-Polling session is out of time, %return_token% = false & Response (In this case client will send a request to this method again)
  7. Error, if current Roll-In Token is out of time
  • authed
  1. Discontinue & Unlink Roll-In token
  2. %return_token% = Request Token & Response

Response

{
    "token": %return_token%
}

request

[public method] This method is a synthetic path, which signs and redirects requests to Monobank. e.g.: POST /request/personal/auth/request turns to a Monobank request POST /personal/auth/request

Server actions

  1. Get Monobank token paired with Request token from X-Token incoming header, else Error
  2. Clone incoming headers %headers%
  3. Clone the raw HTTP request body (e.g. php://input)
  4. Remove Host header from the request (there also can be more inappropriate headers added by your HTTP server, such as SSL or GeoIp-Country-Code). Replace the original X-Token with paired Monobank token
  5. Send the request to Monobank with the same HTTM method as incoming request, get the answer
  6. Clone response headers and answer to output

push/list

[public method] This method lists available Push subscriptions

POST Form Input

Name Value
endpoint Push Subscription endpoint

Ingredient

The ingredient for this Push Server method is Request Token

Server Actions

  1. Get available notification channels:
    1. List your own channels
    2. If you want to support transaction updates, get accounts from GET /client-info Monobank Request
  2. Get subscriptions with such endpoint and Request Token
  3. Mark activated notification channels in list as turned on

Response

[
 {
   // Notification Channel Type
  "type": "sominemo",
   // Notification Channel ID
  "id": "app_updates",
   // Material Display Icon
  "icon": "new_releases",
   // Display name
  "sign": {
   <MCAP Text Token>
  },
   // Channel description
  "description": {
   <MCAP Text Token>
  },
   // Notification channel state (turned on/off)
  "state": false
 },
 ...
]

push/subscribe

Subscribes current Push subscription on a Notification Channel and updates Push Subscriptions for other subscriptions on this Request Token

Ingredient

The ingredient for this Push Server method is Request Token

POST Form Input

Name Value
type Notification Channel ID
id Notification Channel Type
endpoint Push Subscription Endpoint
key Push Subscription Key
auth Push Subscription Auth
expires Push Subscription Expiration time
encoding Push Subscription Encoding
cert Push Server Public Key

Server Actions

  1. Check if cert is the same as Push Server Public Key, else Error
  2. Check if such Notification Channel already exists for this Request Token. If so, skip to 6
  3. Check if it's possible to subscribe on this Notification Channel
  4. Create new subscription on this Notification Channel for this Request Token
  5. Send a success notification on current Push Subscription. If fails, Error
  6. Update Push Subscription for all subscriptions on this Request Token

Response

  { "result": true }

push/unsubscribe

[public method] Deletes subscriptions from current Request Token

Ingredient

The ingredient for this Push Server method is Request Token

POST Form Input

Name Value
endpoint Push Subscription Endpoint
channels JSON marshaled array of Notification Channels, e.g. [{ "type": "string", "id": "string"}]

Server Actions

  1. Delete subscriptions of all specified Notification Channels for this Request Token

Response

  { "result": true }

push/broadcast

[service method] Broadcasts a custom message on a specified Notification Channel

GET Params

Name Value
id Notification Channel ID
type Notification Channel Type

Request Body

JSON marshaled <MCAP Push>

Ingredient

The ingredient for this method is Secret Proof

Server Actions

  1. Check if Secret Proof is correct, else Error
  2. Check if Notification Channel is correct, else Error
  3. Check if Request Body is a correct JSON, else Error
  4. Send the push to each Push Subscription on this Notification Channel. Remove failed ones.

Response

unspecified

nuke

[public method] Deletes all personal data related to the Request Token

Ingredient

The Request ID for this Push Server method is Request Token

Server Actions

  1. Find Mono clientId for the token
  2. Find all tokens for the clientId
  3. Delete all push subscriptions related to the found tokens
  4. Delete all related tokens

Response

  { "status": true }

Подпись запроса

Не путать подпись с шифрованием

1. Ингредиенты

Строка подписи состоит из трёх ингредиентов:

  1. Отметка времени (unix timestamp), который используется в заголовке запроса X-Time
  2. X-Request-Id, если не указано иное. В дальнейшем упоминается как Второй Ингредиент Подписи
  3. Название метода - /%resource% (например /personal/client-info)

Исключения для Второго Ингредиента Подписи

  • /personal/auth/request: разрешения токена (например, sp)
  • /personal/corp/webhook, /personal/corp/webhook: пустая строка

2. Составление строки подписи

Строка подписи представляет собой конкатенацию всех ингредиентов ($ing1.$ing2.$ing3 для PHP, ${ing1}${ing2}${ing3} или ing1+ing2+ing3 для JavaScript)

3. Подпись

  1. Сделайте подпись с помощью OpenSSL или его аналогов, используя следующие параметры:
Свойство Значение
Алгоритм SHA256
Ключ Ваш приватный ключ
  1. Перекодируйте подпись с помощью Base64

  2. Передайте полученную строку в заголовке X-Sign

Примеры

PHP

$key = openssl_get_privatekey("file://private.key", "");
    $str = $time.$t.$url;
    openssl_sign($str, $sig, $key, OPENSSL_ALGO_SHA256);
    openssl_free_key($key);
    return base64_encode($sig);

Python

import base64
import ecdsa
...
data = (timestamp + permissions + url).encode('utf-8')
sign = PrivateSigningKey.sign(data, hashfunc=hashlib.sha256)
signB64 = base64.b64encode(sign)

Go

См. https://github.com/shal/mono
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment