Callback
Quickpay offers a callback service that will notify your system when you create, change or delete a resource. This is useful e.g. when you want to mark an order as paid, update your shops inventory and so on.
The callback service is asynchronous and as such will not interfer with or prolong the processing time of the API request generating a callback - eg. the time your customer will have to wait for payment confirmation.
In the event that your system is not able to receive or correctly process the callback, the callback service will try to deliver its message up to 24 times, with gradually increasing delays between each try.
How it works
When you create, change or delete a resource a HTTP POST request is sent to the callback URL defined in your Account settings.
A callback URL can also be specified in each operation request to the API with the header QuickPay-Callback-Url
, or in each request to the Payment Window with the field callbackurl
.
Request headers
The request headers contains some important information.
Header | Description |
---|---|
QuickPay-Resource-Type | The type of resource that was created, changed or deleted |
QuickPay-Account-ID | The account id of the resource owner - useful if receiving callbacks for several systems on the same url |
QuickPay-API-Version | API version of the callback-generating request |
QuickPay-Checksum-Sha256 | Checksum of the entire raw callback request body - using HMAC with SHA256 as the cryptographic hash function. The checksum is signed using the Account's private key. We strongly recommend that you validate the checksum to ensure that the request is authentic. |
Request body
The request body contains the resource as it exists after the change - ie. it is equivalent to GET /<resource>/<identifier>
.
Response
We expect a response http status code of 2xx
, 302
or 303
to the callback. Otherwise the callback is failed and will be retried after an hour.
When http status in the response is 301
or 307
we redirect the callback to the url in the Location
header.
Order of callbacks
The callbacks are asynchronous and can be delivered in any order. However, callbacks on the same resource are guaranteed to be delivered in the same order the operations (create, change or delete) happen. E.g. callbacks for authorize and capture on the same Payment will always be delivered in that order, but two callbacks on different Payments will come in any order.
Request example
This example shows a callback generated when authorizing a payment:
{
"id": 110376903,
"merchant_id": 5,
"order_id": "14192826166",
"accepted": true,
"type": "Payment",
"text_on_statement": null,
"branding_id": null,
"variables": {},
"currency": "DKK",
"state": "new",
"metadata": {
"type": "card",
"origin": "form",
"brand": "dankort",
"bin": "100002",
"last4": "0006",
"exp_month": 8,
"exp_year": 2022,
"country": "DNK",
"is_3d_secure": false,
"issued_to": null,
"hash": "d345e9e91c4b46f938662OWBm2Ucn2YCzoTby8bMmL2dLFlhInxa",
"number": null,
"customer_ip": "90.185.63.248",
"customer_country": "DK",
"fraud_suspected": false,
"fraud_remarks": [],
"fraud_reported": false,
"fraud_report_description": null,
"fraud_reported_at": null,
"nin_number": null,
"nin_country_code": null,
"nin_gender": null,
"shopsystem_name": null,
"shopsystem_version": null
},
"link": {
"url": "https://payment.quickpay.net/payments/ef45d3ca56c65733e777c4fe0cf0484a536d7329d20f1b9d79c16dab62b80195",
"agreement_id": 11,
"language": "da",
"amount": 100,
"continue_url": null,
"cancel_url": null,
"callback_url": "http://domain.com/callbackurl",
"payment_methods": "",
"auto_fee": false,
"auto_capture": null,
"branding_id": null,
"google_analytics_client_id": null,
"google_analytics_tracking_id": null,
"version": "v10",
"acquirer": null,
"deadline": null,
"framed": true,
"branding_config": {},
"invoice_address_selection": null,
"shipping_address_selection": null,
"customer_email": null
},
"shipping_address": null,
"invoice_address": null,
"basket": [],
"shipping": null,
"operations": [
{
"id": 1,
"type": "authorize",
"amount": 100,
"pending": false,
"qp_status_code": "20000",
"qp_status_msg": "Approved",
"aq_status_code": "000",
"aq_status_msg": "Approved",
"data": {},
"callback_url": "http://domain.com/callbackurl",
"callback_success": true,
"callback_response_code": "200",
"callback_duration": 121,
"acquirer": "nets",
"callback_at": "2018-03-20T08:48:36+00:00",
"created_at": "2018-03-20T08:48:35Z"
}
],
"test_mode": true,
"acquirer": "nets",
"facilitator": null,
"created_at": "2018-03-20T08:48:27Z",
"updated_at": "2018-03-20T08:48:36Z",
"retented_at": null,
"balance": 0,
"fee": null,
"deadline_at": null
}
Checksum validation examples
These examples show how you can authenticate the callback request sent to your server.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
require 'openssl'
def sign(base, private_key)
OpenSSL::HMAC.hexdigest('sha256', private_key, base)
end
# Assuming that you are using Rack - https://github.com/rack/rack
request_body = env["rack.input"].read
checksum = sign(request_body, "your_account_private_key")
if checksum == env["HTTP_QUICKPAY_CHECKSUM_SHA256"]
# Request is authenticated
else
# Request is NOT authenticated
end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
function sign($base, $private_key) {
return hash_hmac("sha256", $base, $private_key);
}
$request_body = file_get_contents("php://input");
$checksum = sign($request_body, "your_account_private_key");
if ($checksum == $_SERVER["HTTP_QUICKPAY_CHECKSUM_SHA256"]) {
// Request is authenticated
} else {
// Request is NOT authenticated
}
?>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/usr/bin/python
# -*- coding: utf-8 -*-
import hashlib
import hmac
import sys
PY3 = sys.version_info[0] >= 3
def sign(base, private_key):
if PY3:
return hmac.new(bytes(private_key, 'utf-8'), bytes(base, 'utf-8'
), hashlib.sha256).hexdigest()
else:
return hmac.new(private_key, base, hashlib.sha256).hexdigest()
# Assuming that you are using Werkzeug - http://werkzeug.pocoo.org/
request_body = environ['wsgi.input'].read()
checksum = sign(request_body, 'your_account_private_key')
if checksum == environ['HTTP_QUICKPAY_CHECKSUM_SHA256']:
# Request is authenticated
else:
# Request is Not authenticated
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import crypto from 'crypto';
// assuming that you are using body-parser - https://github.com/expressjs/body-parser
const apiKey = 'private-key';
const checksum = req.headers['quickpay-checksum-sha256'];
const body = req.body;
const bodyAsString = JSON.stringify(body);
const bodyHashed = crypto
.createHmac('sha256', apiKey)
.update(bodyAsString)
.digest('hex');
if (checksum === bodyHashed) {
// Request is authenticated
} else {
// Request is NOT authenticated
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
string checkSum = Request.Headers["QuickPay-Checksum-Sha256"];
var bytes = new byte[Request.InputStream.Length];
Request.InputStream.Read(bytes, 0, bytes.Length);
Request.InputStream.Position = 0;
string content = Encoding.UTF8.GetString(bytes);
string compute = sign(content, "your_account_private_key");
if (checkSum.Equals(compute))
{
// Request is authenticated
}
else
{
// Request is NOT authenticated
}
private string sign(string base, string api_key) {
var e = Encoding.UTF8;
var hmac = new HMACSHA256(e.GetBytes(api_key));
byte[] b = hmac.ComputeHash(e.GetBytes(base));
var s = new StringBuilder();
for (int i = 0; i < b.Length; i++)
{
s.Append(b[i].ToString("x2"));
}
return s.ToString();
}