Webhook allows you to receive real-time HTTP notifications of events (outgoing / incoming payments). You need to implement a web service to receive and processing of POST-requests according to the requests format.
You need to respond the notification with HTTP 200 OK within 1-2 sec. If QIWI service has no response, it sends next notification in 10 min, then in 1 hour.
Pools of IP-addresses from which QIWI service sends notifications:
- 79.142.16.0/20
- 195.189.100.0/22
- 91.232.230.0/23
- 91.213.51.0/24
If your web service works behinds the firewall, you need to add these IP-addresses to the list of allowed addresses for incoming TCP packets.
- Implement web service for webhook requests. Make sure to implement correctly the digital signature verification.
- Register your service. Please note that its URL original length (before URL-encoding) cannot be longer than 100 symbols.
- Request for signature key.
- Test your service with test request. Empty notification will be sent to your service registered at stage 2.
To change webhook service URL:
- Remove current webhook service.
- Register new webhook service. Please note that its URL original length (before URL-encoding) cannot be longer than 100 symbols.
- Request for new signature key.
- Test your service with test request. Empty notification will be sent to your service registered at stage 2.
Outgoing payments - notification of payment in process
POST /some-hook.php HTTP/1.1
Accept: application/json
Content-type: application/json
Host: example.com
{"hash": "50779a03d90c4fa60ac44dfd158dbceec0e9c57fa4cf4f5298450fdde1868945",
"hookId": "f57f95e2-149f-4278-b2cb-4114bc319727",
"messageId": "f9a197a8-26b6-4d42-aac4-d86b789c373c",
"payment": {"account": "myAccount",
"comment": "My comment",
"commission": Null,
"date": "2018-05-18T16:05:15+03:00",
"errorCode": "0",
"personId": 79254914194,
"provider": 25549,
"signFields": "sum.currency,sum.amount,type,account,txnId",
"status": "WAITING",
"sum": {"amount": 1.73, "currency": 643},
"total": {"amount": 1.73, "currency": 643},
"txnId": "13117338074",
"type": "OUT"},
"test": false,
"version": "1.0.0"}
Outgoing payment - notification of successful payment
POST /some-hook.php HTTP/1.1
Accept: application/json
Content-type: application/json
Host: example.com
{"hash": "50779a03d90c4fa60ac44dfd158dbceec0e9c57fa4cf4f5298450fdde1868945",
"hookId": "f57f95e2-149f-4278-b2cb-4114bc319727",
"messageId": "6e2a0e32-4c8d-4fe2-9eed-fe3b6a726ff4",
"payment": {"account": "thedandod",
"comment": "My comment",
"commission": {"amount": 0.0, "currency": 643},
"date": "2018-05-18T16:05:15+03:00",
"errorCode": "0",
"personId": 79254914194,
"provider": 25549,
"signFields": "sum.currency,sum.amount,type,account,txnId",
"status": "SUCCESS",
"sum": {"amount": 1.73, "currency": 643},
"total": {"amount": 1.73, "currency": 643},
"txnId": "13117338074",
"type": "OUT"},
"test": false,
"version": "1.0.0"}
Outgoing payments - notification of unsuccessful payment
POST /some-hook.php HTTP/1.1
Accept: application/json
Content-type: application/json
Host: example.com
{"hash": "0637b07b1018d76585db26b0f8077016b12996006429e22a7dc5b6982710a1ef",
"hookId": "f57f95e2-149f-4278-b2cb-4114bc319727",
"messageId": "1133873b-9bb6-4adb-9bfe-7be3a9aa999f",
"payment": {"account": "borya241203",
"comment": "",
"commission": None,
"date": "2018-05-20T05:19:16+03:00",
"errorCode": "5",
"personId": 79254914194,
"provider": 25549,
"signFields": "sum.currency,sum.amount,type,account,txnId",
"status": "ERROR",
"sum": {"amount": 1.01, "currency": 643},
"total": {"amount": 1.01, "currency": 643},
"txnId": "13126423989",
"type": "OUT"},
"test": false,
"version": "1.0.0"}
Incoming payment - notification of successful payment
POST /some-hook.php HTTP/1.1
Accept: application/json
Content-type: application/json
Host: example.com
{"hash": "a56ed0090fa3fd2fd0b002ed80f85a120037a6a85f840938888275e1631da96f",
"hookId": "8c79f60d-0272-476b-b120-6e7629467328",
"messageId": "bba24947-ab5f-4b33-881b-738fc3a4c9e1",
"payment": {"account": "79042426915",
"comment": "Replenishing wallet",
"commission": {"amount": 0.0, "currency": 643},
"date": "2018-03-25T13:16:48+03:00",
"errorCode": "0",
"personId": 79645265240,
"provider": 7,
"signFields": "sum.currency,sum.amount,type,account,txnId",
"status": "SUCCESS",
"sum": {"amount": 1.09, "currency": 643},
"total": {"amount": 1.09, "currency": 643},
"txnId": "12565018935",
"type": "IN"},
"test": false,
"version": "1.0.0"}
Each notification is an incoming POST-request with single payment data in JSON body. JSON scheme is as followed:
Field | Type | Description |
---|---|---|
hookId | String (UUID) | Unique webhook id |
messageId | String (UUID) | Unique notification id |
payment | Object | Payment data |
payment.txnId | String | QIWI Wallet transaction ID |
payment.account | String | For outgoing payments - recipients account number. For incoming payments - sender number, self-service kiosk number, or top-up agent name |
payment.signFields | String | A list of fields in payment object (separated by comma) to use for HmacSHA256 hash calculation of notification signature (see hash field) |
payment.personId | Integer | Your wallet number |
payment.date | String DateTime | Payment date/time, in Moscow time zone, as YYYY-MM-DD'T'hh:mm:ss+03:00 |
payment.errorCode | String | Payment error code |
payment.type | String | Payment type:IN - wallet top-up, OUT - payment |
payment.status | String | Payment status:WAITING - payment in process,SUCCESS - successful payment,ERROR - payment error. |
payment.provider | Integer | Provider ID in QIWI Wallet |
payment.comment | String | Transaction comment |
payment.sum | Object | Payment amount data |
---- | ---- | --- |
sum.amount | Number(Decimal) | Amount |
sum.currency | Number(3) | Currency code |
---- | ---- | ---- |
payment.commission | Object | Payment commission |
---- | ---- | ---- |
commission.amount | Number(Decimal) | Commission amount |
commission.currency | Number(3) | Currency code |
---- | ---- | ---- |
payment.total | Object | Total payment amount |
---- | ---- | ---- |
total.amount | Number(Decimal) | Amount |
total.currency | Number(3) | Currency code |
---- | ---- | ---- |
test | Boolean | Flag indicating test notification |
version | String | Webhook API version |
hash | String | Hash of the notification's digital signature |
<?php
//Procedure returns string of sorted values from notification parameters and signature hash for verification
function getReqParams(){
//Make sure that it is a POST request.
if(strcasecmp($_SERVER['REQUEST_METHOD'], 'POST') != 0){
throw new Exception('Request method must be POST!');
}
//Receive the RAW post data.
$content = trim(file_get_contents("php://input"));
//Attempt to decode the incoming RAW post data from JSON.
$decoded = json_decode($content, true);
//If json_decode failed, the JSON is invalid.
if(!is_array($decoded)){
throw new Exception('Received content contained invalid JSON!');
}
//Check if test
if ($decoded['test'] == 'true') {
throw new Exception('Test!');
}
// String of parameters
$reqparams = $decoded['payment']['sum']['currency'] . '|' . $decoded['payment']['sum']['amount'] . '|'. $decoded['payment']['type'] . '|' . $decoded['payment']['account'] . '|' . $decoded['payment']['txnId'];
// Signature
foreach ($decoded as $name=>$value) {
if ($name == 'hash') {
$SIGN_REQ = $value;
}
}
return [$reqparams, $SIGN_REQ];
}
// Resulted data
$Request = getReqParams();
// Base64 encoded key for decryption (method /hook/{hookId}/key)
$NOTIFY_PWD = "JcyVhjHCvHQwufz+IHXolyqHgEc5MoayBfParl6Guoc=";
// Get SHA-256 hash of the string and encrypt with your webhook key
$reqres = hash_hmac("sha256", $Request[0], base64_decode($NOTIFY_PWD));
// Verify signature
if (hash_equals($reqres, $Request[1])) {
$error = array('response' => 'OK');
}
else $error = array('response' => 'error');
//Response
header('Content-Type: application/json');
$jsonres = json_encode($error);
echo $jsonres;
error_log('error code' . $jsonres);
?>
import base64
import hmac
import hashlib
# Base64 encoded key (get it with /hook/{hookId}/key request)
webhook_key_base64 = 'JcyVhjHCvHQwufz+IHXolyqHgEc5MoayBfParl6Guoc='
# notification parameters
data = '643|1|IN|+79161112233|13353941550'
webhook_key = base64.b64decode(bytes(webhook_key_base64,'utf-8'))
print(hmac.new(webhook_key, data.encode('utf-8'), hashlib.sha256).hexdigest())
To verify signature of a notification, proceed with the following:
- Take values of fields specified in
payment.signFields
field of the notification JSON (in the same order) as Strings. - Join them with
|
separator. - Encode the resulted string with SHA-256 and signature key.
- Compare obtained value with
hash
field of the notification.
Example of signature verification (see also PHP procedure on the right tab):
- You get signature key, encoded in Base64:
JcyVhjHCvHQwufz+IHXolyqHgEc5MoayBfParl6Guoc=
- You get notification:
{"messageId":"7814c49d-2d29-4b14-b2dc-36b377c76156","hookId":"5e2027d1-f5f3-4ad1-b409-058b8b8a8c22","payment":{"txnId":"13353941550","date":"2018-06-27T13:39:00+03:00","type":"IN","status":"SUCCESS","errorCode":"0","personId":78000008000,"account":"+79161112233","comment":"","provider":7,"sum":{"amount":1,"currency":643},"commission":{"amount":0,"currency":643},"total":{"amount":1,"currency":643},"signFields":"sum.currency,sum.amount,type,account,txnId"},"hash":"76687ffe5c516c793faa46fafba0994e7ca7a6d735966e0e0c0b65eaa43bdca0","version":"1.0.0","test":false}
- Join values of the fields specified in
signFields
field (sum.currency,sum.amount,type,account,txnId
):
643|1|IN|+79161112233|13353941550
- The obtained string is encoded with SHA-256 and the Base64-decoded key from step 1:
f05c4e7bdf00620205d47696d77f924bfd3ba4d02b0398ac8a626e737dc27243
Result coincides withhash
field from the notification. The verification is successful.
curl -X PUT \
"https://edge.qiwi.com/payment-notifier/v1/hooks?hookType=1¶m=http%3A%2F%2Fexample.com%2Fcallbacks%2F&txnType=2" \
-H "accept: */*" \
-H "authorization: Bearer <API token>"
PUT /payment-notifier/v1/hooks?hookType=1¶m=http%3A%2F%2Fexample.com%2Fcallbacks%2F&txnType=2 HTTP/1.1
Host: edge.qiwi.com
Authorization: Bearer <API token>
User-Agent: ****
-
- Authorization: Bearer ***
- Accept: application/json
- Send parameters in the request query. All parameters are required.
Name | Type | Description |
---|---|---|
hookType | Integer | Webhook type. Only 1 . |
param | URL-encoded | Service URL. URL original length (before URL-encoding) must be within 100 symbols. URL must be accessible from the Internet. |
txnType | String | Choose type of transactions for notifications: 0 - only incoming transactions (wallet top-up); 1 - only outgoing transactions (payments); 2 - all transactions |
HTTP/1.1 200 OK
Content-Type: application/json
{
"hookId":"d63a8729-f5c8-486f-907d-9fb8758afcfc",
"hookParameters":{
"url":"http://example.com/callbacks/"
},
"hookType":"WEB",
"txnType":"BOTH"
}
Response in JSON.
Field | Type | Description |
---|---|---|
hookId | String | Webhook service UUID |
hookParameters | Object | Webhook service parameters |
hookParameters.url | String | Webhook service URL |
hookType | String | Webhook type (only WEB ) |
txnType | String | Transactions type for notifications (IN - incoming, OUT - outgoing, BOTH - all) |
curl -X DELETE \
"https://edge.qiwi.com/payment-notifier/v1/hooks/<hook-id>" \
-H "accept: */*" \
-H "authorization: Bearer <API token>"
DELETE /payment-notifier/v1/hooks/d63a8729-f5c8-486f-907d-9fb8758afcfc HTTP/1.1
Host: edge.qiwi.com
Authorization: Bearer <API token>
User-Agent: ****
- hookId - webhook UUID
-
- Authorization: Bearer ***
- Accept: application/json
HTTP/1.1 200 OK
Content-Type: application/json
{
"response":"Hook deleted"
}
Response in JSON.
Field | Type | Description |
---|---|---|
response | String | Operation result |
Each notification contains digital signature encoded by secret key. Use this request to get the key.
curl -X GET \
"https://edge.qiwi.com/payment-notifier/v1/hooks/<hook-id>/key" \
-H "accept: */*" \
-H "accept: */*" \
-H "authorization: Bearer <API token>"
GET /payment-notifier/v1/hooks/d63a8729-f5c8-486f-907d-9fb8758afcfc/key HTTP/1.1
Host: edge.qiwi.com
Authorization: Bearer <API token>
User-Agent: ****
- hookId — webhook UUID
-
- Authorization: Bearer ***
- Accept: application/json
HTTP/1.1 201 Created
Content-Type: application/json
{
"key":"L8UVF3JkLVUr6r70LiE0A9/5WoGGwWKG2pI/e+l/9fs="
}
Response in JSON.
Field | Type | Description |
---|---|---|
key | String | Base64-encoded key |
Changes the secret key for notifications signature.
curl -X POST \
"https://edge.qiwi.com/payment-notifier/v1/hooks/<hook-id>/newkey" \
-H "accept: */*" \
-H "authorization: Bearer <API token>"
POST /payment-notifier/v1/hooks/d63a8729-f5c8-486f-907d-9fb8758afcfc/newkey HTTP/1.1
Host: edge.qiwi.com
Authorization: Bearer <API token>
User-Agent: ****
- hookId - webhook UUID
-
- Authorization: Bearer ***
- Accept: application/json
HTTP/1.1 201 Created
Content-Type: application/json
{
"key":"OikS4/CcIbSf+yYGnLbnOige8RGoYmGxs/LNMwkJy7Q="
}
Response in JSON.
Field | Type | Description |
---|---|---|
key | String | Base64-encoded new key |
Gets the active webhook service linked to your wallet.
curl -X GET \
"https://edge.qiwi.com/payment-notifier/v1/hooks/active" \
-H "accept: */*" \
-H "accept: */*" \
-H "authorization: Bearer <API token>"
GET /payment-notifier/v1/hooks/active HTTP/1.1
Host: edge.qiwi.com
Authorization: Bearer <API token>
User-Agent: ****
-
- Authorization: Bearer ***
- Accept: application/json
HTTP/1.1 200 OK
Content-Type: application/json
{
"hookId":"d63a8729-f5c8-486f-907d-9fb8758afcfc",
"hookParameters":{
"url":"http://example.com/callbacks/"
},
"hookType":"WEB",
"txnType":"BOTH"
}
Response in JSON.
Field | Type | Description |
---|---|---|
hookId | String | Active webhook UUID |
hookParameters | Object | Webhook service parameters |
hookParameters.url | String | Webhook URL |
hookType | String | Webhook type (only WEB ) |
txnType | String | Transactions type for notifications (IN - incoming (wallet topup), OUT - outgoing (payments), BOTH - all) |
Use this request to test your webhook service. As a result of the request, empty test notification is sent to the URL of the active webhook service.
curl -X GET \
"https://edge.qiwi.com/payment-notifier/v1/hooks/test" \
-H "accept: */*" \
-H "authorization: Bearer <API token>"
GET /payment-notifier/v1/hooks/test HTTP/1.1
Host: edge.qiwi.com
Authorization: Bearer <API token>
User-Agent: ****
-
- Authorization: Bearer ***
- Accept: application/json
HTTP/1.1 200 OK
Content-Type: application/json
{
"response":"Webhook sent"
}
Response in JSON.
Field | Type | Description |
---|---|---|
response | String | Notification on the request |