Webhooks
Цей вебхук сповіщає вашу систему про зміни статусу рахунку. Бекенд надсилає POST-запит на вказану вами URL-адресу, включаючи підпис для забезпечення безпеки. Нижче наведено деталі функціоналу вебхука та інструкції з його обробки.
Як працює вебхук
- 1
Подія, що тригерить вебхук:
- Вебхук викликається при зміні статусу рахунку.
- 2
Доставка POST-запиту:
- Бекенд намагається надіслати POST-запит на вказану URL-адресу.
- У разі невдалої спроби (відсутність відповіді HTTP 200 OK) виконується до 3 повторних спроб.
- 3
Заголовки:
- Запит містить такі заголовки:
- Content-Type: application/json
- X-Sign: Містить підпис тіла запиту.
- Запит містить такі заголовки:
- 4
Безпека:
- Заголовок X-Sign гарантує автентичність та цілісність даних вебхука.
- Підпис створюється за допомогою стандарту ECDSA.
- Приклади перевірки підпису наведено нижче.
Механізм повторення запитів:
- Якщо сторонній сервер не відповідає зі статусом HTTP 200 OK, бекенд еквайрингу повторює спробу до трьох разів.
Аутентифікація:
- Для забезпечення безпеки використовується параметр X-Sign у заголовках запиту.
- X-Sign — це підпис, який генерується на основі тіла запиту за стандартом ECDSA (еліптична криптографія для підписів).
Приклади верифікації підпису webhook
Go
package main
import (
"crypto/ecdsa"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/pem"
"log"
)
var (
// example pubkey, you should receive one at https://api.monobank.ua/api/merchant/pubkey
pubKeyBase64 = `LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFQUc1LzZ3NnZubGJZb0ZmRHlYWE4vS29CbVVjTgo3NWJSUWg4MFBhaEdldnJoanFCQnI3OXNSS0JSbnpHODFUZVQ5OEFOakU1c0R3RmZ5Znhub0ZJcmZBPT0KLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg==`
// value from X-Sign header in webhook request
xSignBase64 = `MEUCIQC/mVKhi8FKoayul2Mim3E2oaIOCNJk5dEXxTqbkeJSOQIgOM0hsW0qcP2H8iXy1aQYpmY0SJWEaWur7nQXlKDCFxA=`
// webhook request body bytes
bodyBytes = []byte(`{
"invoiceId": "p2_9ZgpZVsl3",
"status": "created",
"failureReason": "string",
"amount": 4200,
"ccy": 980,
"finalAmount": 4200,
"createdDate": "2019-08-24T14:15:22Z",
"modifiedDate": "2019-08-24T14:15:22Z",
"reference": "84d0070ee4e44667b31371d8f8813947",
"cancelList": [
{
"status": "processing",
"amount": 4200,
"ccy": 980,
"createdDate": "2019-08-24T14:15:22Z",
"modifiedDate": "2019-08-24T14:15:22Z",
"approvalCode": "662476",
"rrn": "060189181768",
"extRef": "635ace02599849e981b2cd7a65f417fe"
}
]
}`)
)
func main() {
pubKeyBytes, err := base64.StdEncoding.DecodeString(pubKeyBase64)
if err != nil {
panic(err)
}
signatureBytes, err := base64.StdEncoding.DecodeString(xSignBase64)
if err != nil {
panic(err)
}
block, _ := pem.Decode(pubKeyBytes)
if block == nil {
panic("invalid pem")
}
genericPubKey, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
panic(err)
}
pubKey, ok := genericPubKey.(*ecdsa.PublicKey)
if !ok {
panic("invalid key")
}
hash := sha256.Sum256(bodyBytes)
ok = ecdsa.VerifyASN1(pubKey, hash[:], signatureBytes)
if !ok {
panic("invalid X-Sign")
}
log.Println("OK")
}
Python
import base64
import hashlib
import ecdsa
# example pubkey, you should receive one at https://api.monobank.ua/api/merchant/pubkey
pub_key_base64 = "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFQUc1LzZ3NnZubGJZb0ZmRHlYWE4vS29CbVVjTgo3NWJSUWg4MFBhaEdldnJoanFCQnI3OXNSS0JSbnpHODFUZVQ5OEFOakU1c0R3RmZ5Znhub0ZJcmZBPT0KLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg=="
# value from X-Sign header in webhook request
x_sign_base64 = "MEUCIQC/mVKhi8FKoayul2Mim3E2oaIOCNJk5dEXxTqbkeJSOQIgOM0hsW0qcP2H8iXy1aQYpmY0SJWEaWur7nQXlKDCFxA="
# webhook request body bytes
body_bytes = b'''{
"invoiceId": "p2_9ZgpZVsl3",
"status": "created",
"failureReason": "string",
"amount": 4200,
"ccy": 980,
"finalAmount": 4200,
"createdDate": "2019-08-24T14:15:22Z",
"modifiedDate": "2019-08-24T14:15:22Z",
"reference": "84d0070ee4e44667b31371d8f8813947",
"cancelList": [
{
"status": "processing",
"amount": 4200,
"ccy": 980,
"createdDate": "2019-08-24T14:15:22Z",
"modifiedDate": "2019-08-24T14:15:22Z",
"approvalCode": "662476",
"rrn": "060189181768",
"extRef": "635ace02599849e981b2cd7a65f417fe"
}
]
}'''
if __name__ == '__main__':
pub_key_bytes = base64.b64decode(pub_key_base64)
signature_bytes = base64.b64decode(x_sign_base64)
pub_key = ecdsa.VerifyingKey.from_pem(pub_key_bytes.decode())
ok = pub_key.verify(signature_bytes, body_bytes, sigdecode=ecdsa.util.sigdecode_der, hashfunc=hashlib.sha256)
if ok:
print("OK")
else:
print("NOT OK")
Php
<?php
// example pubkey, you should receive one at https://api.monobank.ua/api/merchant/pubkey
$pubKeyBase64 = "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFQUc1LzZ3NnZubGJZb0ZmRHlYWE4vS29CbVVjTgo3NWJSUWg4MFBhaEdldnJoanFCQnI3OXNSS0JSbnpHODFUZVQ5OEFOakU1c0R3RmZ5Znhub0ZJcmZBPT0KLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg==";
// value from X-Sign header in webhook request
$xSignBase64 = "MEUCIQC/mVKhi8FKoayul2Mim3E2oaIOCNJk5dEXxTqbkeJSOQIgOM0hsW0qcP2H8iXy1aQYpmY0SJWEaWur7nQXlKDCFxA=";
$message = '{
"invoiceId": "p2_9ZgpZVsl3",
"status": "created",
"failureReason": "string",
"amount": 4200,
"ccy": 980,
"finalAmount": 4200,
"createdDate": "2019-08-24T14:15:22Z",
"modifiedDate": "2019-08-24T14:15:22Z",
"reference": "84d0070ee4e44667b31371d8f8813947",
"cancelList": [
{
"status": "processing",
"amount": 4200,
"ccy": 980,
"createdDate": "2019-08-24T14:15:22Z",
"modifiedDate": "2019-08-24T14:15:22Z",
"approvalCode": "662476",
"rrn": "060189181768",
"extRef": "635ace02599849e981b2cd7a65f417fe"
}
]
}';
$signature = base64_decode($xSignBase64);
$publicKey = openssl_get_publickey(base64_decode($pubKeyBase64));
$result = openssl_verify($message, $signature, $publicKey, OPENSSL_ALGO_SHA256);
echo $result === 1 ? "OK" : "NOT OK";
NodeJs
const crypto = require('crypto');
// example pubkey, you should receive one at https://api.monobank.ua/api/merchant/pubkey
let pubKeyBase64 = "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFQUc1LzZ3NnZubGJZb0ZmRHlYWE4vS29CbVVjTgo3NWJSUWg4MFBhaEdldnJoanFCQnI3OXNSS0JSbnpHODFUZVQ5OEFOakU1c0R3RmZ5Znhub0ZJcmZBPT0KLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg==";
// value from X-Sign header in webhook request
let xSignBase64 = "MEUCIQC/mVKhi8FKoayul2Mim3E2oaIOCNJk5dEXxTqbkeJSOQIgOM0hsW0qcP2H8iXy1aQYpmY0SJWEaWur7nQXlKDCFxA=";
let message = `{
"invoiceId": "p2_9ZgpZVsl3",
"status": "created",
"failureReason": "string",
"amount": 4200,
"ccy": 980,
"finalAmount": 4200,
"createdDate": "2019-08-24T14:15:22Z",
"modifiedDate": "2019-08-24T14:15:22Z",
"reference": "84d0070ee4e44667b31371d8f8813947",
"cancelList": [
{
"status": "processing",
"amount": 4200,
"ccy": 980,
"createdDate": "2019-08-24T14:15:22Z",
"modifiedDate": "2019-08-24T14:15:22Z",
"approvalCode": "662476",
"rrn": "060189181768",
"extRef": "635ace02599849e981b2cd7a65f417fe"
}
]
}`
let signatureBuf = Buffer.from(xSignBase64, 'base64');
let publicKeyBuf = Buffer.from(pubKeyBase64, 'base64');
let verify = crypto.createVerify('SHA256');
verify.write(message);
verify.end();
let result = verify.verify(publicKeyBuf, signatureBuf);
console.log(result === true ? "OK" : "NOT OK");