Links

Payment verification

As soon as payment or refund succeeds or fails, kevin. sends you a webhook. Webhooks are just HTTP POST requests to your web server. Your Webhook-URL must return a response with a 200 HTTP status code. Any other HTTP response code will be considered as a failure. It is not required for your webhook response to return any content. We will retry failed webhooks periodically for up to two days until we receive a response with 200 HTTP status code.
Webhook is just a signal about the final payment status, which cannot be changed. If you need any additional information about the payment, you have to fetch it independently using the getPayment endpoint or attach your own query parameters to your Webhook-URL. Below you can find the example of a webhook request:
Bank Payment
Card Payment
Refund
{
"id": "e4dd60bb-574f-4a13-910a-57c9795d905f",
"bankStatus": "ACSC",
"statusGroup": "completed",
"type": "PAYMENT"
}
{
"id": "e4dd60bb-574f-4a13-910a-57c9795d905f",
"cardStatus": "payment_success",
"statusGroup": "completed",
"type": "PAYMENT"
}
{
"id": "1",
"paymentId": "e4dd60bb-574f-4a13-910a-57c9795d905f",
"statusGroup": "completed",
"type": "PAYMENT_REFUND"
}
It’s strongly advised to use a raw request body for the hash computing process.
To test your webhook listener during the integration process our CLI tool could be used.
Webhooks do not confirm that money is received. If bank supports instant payments, funds are received in a couple of minutes. Otherwise, the transaction will be settled within 1-3 work days.
In order to meet all security requirements, kevin. signs every webhook request. The request contains two headers:
  • X-Kevin-Timestamp - the timestamp in milliseconds when the request was sent by kevin.
  • X-Kevin-Signature - the signature which is computed using the HMAC-SHA256 algorithm.
You can validate the signature by combining the uppercase HTTP method of the request, the request URL, timestamp and the request body into one single string and then generating it using the HMAC-SHA256 algorithm with your endpointSecret.
ATTENTION: Client Secret is different key. If you do not have yourEndpoint Secret, please email [email protected].

Computing signature hash

We recommend rejecting the webhook request if the signature is older than 5 minutes.
Below you can find the signature generation code for webhook request confirmation:
PHP
Node.js
Pseudocode
The example below is written using kevin. PHP library.
1
use Kevin\SecurityManager;
2
3
$endpointSecret = 'your-endpoint-secret';
4
$webhookUrl = 'your-webhook-url';
5
6
// Timestamp is provided in milliseconds
7
$timestampTimeout = 300000;
8
9
$requestBody = file_get_contents('php://input');
10
$headers = getallheaders();
11
12
$isValid = SecurityManager::verifySignature(
13
$endpointSecret,
14
$requestBody,
15
$headers,
16
$webhookUrl,
17
$timestampTimeout
18
);
19
20
http_response_code(200);
Make sure that received webhook is authentic. The example below is written using kevin. Node.js library.
1
const kevin = require('@kevin.eu/kevin-platform-client');
2
3
const endpointSecret = 'my-endpoint-secret';
4
5
const securityManager = new kevin.SecurityManager(endpointSecret);
6
7
// webhook request headers object
8
const headers = req.headers;
9
10
// webhook request body object
11
const body = req.body;
12
13
// URL to which webhook was requested
14
const webhookUrl = 'https://example.com/notify?orderId=123';
15
16
// Timestamp timeout in milliseconds
17
const timestampTimeout = '300000';
18
19
const isWebhookSignatureValid = securityManager.verifySignature(body, headers, webhookUrl, timestampTimeout);
httpMethod = 'POST'
requestUrl = 'https://yourapp.com/notify'
timestamp = '1600000000000'
endpointSecret = 'SECRET'
// Bank payment payload:
requestBody = '{"id":"e4dd60bb-574f-4a13-910a-57c9795d905f","bankStatus":"ACSC","statusGroup":"completed","type":"PAYMENT"}'
expectedSignature = 0a3ac91865c78ac9b675129f24ee3f25a71b02d1e83976833f0f139db6508777
// Card payment payload:
requestBody = '{"id":"e4dd60bb-574f-4a13-910a-57c9795d905f","cardStatus":"paid","statusGroup":"completed","type":"PAYMENT"}'
expectedSignature = 54cf5691f8d121f3b79bc1d102709975ff2ad39143e189043841c9c55fbe0902
// Hybrid payment payload:
requestBody = '{"id":"e4dd60bb-574f-4a13-910a-57c9795d905f","hybridStatus":"created","statusGroup":"completed","type":"PAYMENT"}'
expectedSignature = 4492b9761e7897b7f532706ea7bf87c1f9f7d6f1513d10c1c76ee0e41b9986ee
data = httpMethod + requestUrl + timestamp + requestBody
signature = HMAC_SHA256(data, endpointSecret)