- 21 Jun 2023
- DarkLight
Webhook integration
- Updated on 21 Jun 2023
- DarkLight
To set up a Webhook, you must first create an endpoint on which notifications will be sent. This endpoint must be a URL that accepts HTTP POST requests. Once the endpoint has been generated, you must configure the Webhook in the Zenkipay portal.
Configuration of the webhook in the commerce portal
To configure webhooks, enter the portal in the settings/webhooks menu.
In the Webhooks section on the endpoints tab, all configured endpoints are listed. To set up an endpoint, select the Add Endpoint option
You need to follow the steps below:
- Add the url of your endpoint, optionally, you can attach a description of your endpoint.
- Select the event that will be notified to the endpoint in the Message Filtering option, you must choose at least one event to report.
- Optionally, you can configure the rate limit, in the Advanced Configuration section.
🔒 Authentication: Once your webhook is configured, you can add authentication with the HTTP Basic authentication standard or through the use of authentication token (the latter is the recommended mechanism).
✅ Tests: You can test the webhook in the Advanced/Testing section, once the endpoint is registered you can test the available events, in which a notification will be sent with test data, together with this you can perform connectivity tests with the ping option.
Webhook signature
Each notification that is sent is signed as a security measure. The signature of the Webhook allows the trade to validate the origin of the notifications and process the notifications with a valid origin, to relizar the validation the secret signature of your endpoint is necessary.
Once your endpoint is configured you can get the secret signature, in the endpoints section and select your configured endpoint.
If you need to update or change the secret signature you can do it in the same section, selecting the "Rotate secret" option.
Logs
In the Logs section, the notifications that are sent to the configured webhook are listed, in each notification the details of the message, the attempts of the notification and the details of the responses of each notification are displayed.
Activity
This section shows graphically the sending of messages, to be able to see in a summarized way the activity of notifications to your webhook.
What events are notified?
Payment completed
This event notification is sent to the merchant when an order has been successfully paid for by the buyer.
eventType: order.pay
flatData: To receive the Order object when the order.pay event is reported.
Reimbursement
Event notification when a refund is made by the merchant for the buyer.
eventType: order.refund
flatData: To be receive the Refund object when the order.recast event is reported.
Check Webhooks
As a security pattern, each webhook and its metadata is signed with a unique key for each endpoint. This signature can be used to verify that the webhook actually comes from Zenkipay, and only process it if the source is valid. Each webhook call includes three headers with additional information that are used for verification:
- svix-id: Unique identifier of the webhook message. This identifier is unique for all messages.
- svix-signature: Base64-encoded signature.
- svix-timestamp: Epoch timestamp.
Verification through the official libraries of our supplier Svix
It is required to install the Svix libraries:
npm install svix
// Or
yarn add svix
composer require svix/svix
// Gradle: Añade esta dependencia
implementation "com.svix:svix:0.68.0"
// Maven: Añade esta dependencia en el archivo POM:
<dependency>
<groupId>com.svix</groupId>
<artifactId>svix</artifactId>
<version>0.68.0</version>
</dependency>
Next, check the webhooks using the code below. The payload is the body (string) of the request, and the headers are the headers passed in the request.
To perform the validation it is necessary to the secret signature, here we tell you how to get secret signature.
import { Webhook } from "svix";
const secret = "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw";
// Las cabeceras son enviadas por cada notificación
const headers = {
"svix-id": "msg_p5jXN8AQM9LWM0D4loKWxJek",
"svix-timestamp": "1614265330",
"svix-signature": "v1,g0hM9SsE+OTPJTGt/tmIKtSyZlE3uFJELVlNIOLJ1OE=",
};
const payload = '{"algorithm":"RSA","encryptedData":"z7YjgSyx0VzXlDGNC4fjjk1IC69qKN8rRLSItUDY9WXQFgr98ORq/ieJunuCucwk6hmrM9CZAlszE/LD/qSeUtOUcv28ngjobZ5UD+zDqLOeqC5KqHtP0I48L1wC+epXMntsd/KxslWh0+s076K8hZFg7dgJOy2HS46tytNX7AAbEzuQouQo3R0OGV//asG3POej3VQTyRTzKVoRDOO7cVGgNenI4AjfAjUJu+gcOzHqrAj5qr92TEZOZf45+pAk6p5nrfL42NBThO8GB3pXQr2/k74HpkFmVXcZJRB7RDSGfhsFCsnDFZ4N4mHQJWc1/u00z7oGzymPSDGQBEUjzIwGbjBLLDHxdCGKWuwdUq5hAH8Nk55HGOycou7ciBBXOl8E3iTaSxldqOkFLpvkMQ6G2i6dH/1ERKxx61LtQveetkGGMPaLRlsgrUGJNftuKNGEMMQgxn4JykppxHqW3KBlzNhpFUn3QELIctk5SoV12XUDVWi4yhd49F0QlbqfDbRN7ogXo12/SYhUEBS4Wa2uo/mtVKkAdo+GYLtgcggP25y+Qw/I5CenBMJtm2mVFi1b/9AwPaDQo+Yd4S7SrGPyhvcRJcSareIyCXIFSDq6j40qPxGclUv0MLHdwiqxcmmiCP9PQwSnKstCNPBx+IN91E6UfnyBYBhXOWFPZqyHG5OtdBfrx9pIa+0TtFiMBbVGUDidj5QkslyLOZ5Zhx+RMOz+47GpiSg9LaObfJdH4vRsbsZgufvt5hceGE6+lUn3zQzTcwPLaEsQv4HsNMEUKW+tt8K6ZB3GLWaWtII4g0gVlQi2T5P4ZsvBFXf2YJFj4cAL21JVcRanjD2vYk0SbYyuOM2fpBtMJR8pIbVTzdyEw3pPQdyHo4LlbYBFkM3DaXxum9qHr2MHFeAefwJRM79ou5laulfj4nqBPi6hhfT1Z9r9ToDPujOtH+0jRnTIPs4zAWY6rXzUivPJkcu3iqUsqCZcQaU5SHhKli/bHakINyRyTd1ozpdrMsE3rn2VorpgJyVDT47/Bh+xG0F8lCZKofgh4w7DTRxOcIJwkUaPqu30lHHbmc8q0JfGXOgTc496TyjdFx4R529DMzDDkSCVFKp3z8qnG46WsIOIGCp2OXvIiSIihyQFO3FnBx16NbdVlicnTwov4TUPRcsYDIx0p33c3hxOmn1RR1aygYx7XvG3tuMS8ktpfq12ENy3zwpeOit1b8ylnBHwEdaAENaVy03TOLMxIj8rZSfj2AXhnwAKPMLE1AWKufE7OkQAGg2JyJ/H5wB69k9FjwmG0UbkpDGCHNKTMSmwWC6ppV08g/VqUxP50cgXdk19u7Atr1AjCmDXdkFJXYeYwg==","flatData":"","keySize":4096}"';
const wh = new Webhook(secret);
// Lanza una excepción en caso de error, devuelve el contenido verificado en caso de éxito
const payload = wh.verify(payload, headers);
// import using composers autoload
require_once('vendor/autoload.php');
// or manually
require_once('/path/to/svix/php/init.php');
$payload = '{"test": 2432232314}';
// Las cabeceras son enviadas por cada notificación
$header = array(
'svix-id' => 'msg_p5jXN8AQM9LWM0D4loKWxJek',
'svix-timestamp' => '1614265330',
'svix-signature' => 'v1,g0hM9SsE+OTPJTGt/tmIKtSyZlE3uFJELVlNIOLJ1OE=',
);
// Lanza una excepción en caso de error, devuelve el contenido verificado en caso de éxito
$wh = new \Svix\Webhook('whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw');
$json = $wh->verify($payload, $header);
import com.svix.Webhook;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.bind.annotation.RequestBody;
import javax.servlet.http.HttpServletRequest;
import java.net.http.HttpHeaders;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
public class VerificationZenkiWebhookMessage {
private static final String HEADER_MESSAGE_ID = "svix-id";
private static final String HEADER_MESSAGE_SIGNATURE = "svix-signature";
private static final String HEADER_MESSAGE_TIMESTAMP = "svix-timestamp";
public void verificationZenkiWebhookMessage(@RequestBody String payload, HttpServletRequest httpRequest) throws Exception {
String secret = "<signed secret>";
Map<String, List<String>> headersMap = Collections.list(httpRequest.getHeaderNames())
.stream()
.collect(Collectors.toMap(
Function.identity(),
h -> Collections.list(httpRequest.getHeaders(h))
));
HttpHeaders headers = HttpHeaders.of(headersMap, (headerName, headerValue) -> {
return StringUtils.isNotEmpty(headerValue) &&
Arrays.asList(HEADER_MESSAGE_ID, HEADER_MESSAGE_SIGNATURE, HEADER_MESSAGE_TIMESTAMP).stream().anyMatch(headerValidate -> headerValidate.equals(headerName));
});
//Ejemplo del contenido de una notificacion
// String payload = "{\"algorithm\":\"RSA\",\"encryptedData\":\"z7YjgSyx0VzXlDGNC4fjjk1IC69qKN8rRLSItUDY9WXQFgr98ORq/ieJunuCucwk6hmrM9CZAlszE/LD/qSeUtOUcv28ngjobZ5UD+zDqLOeqC5KqHtP0I48L1wC+epXMntsd/KxslWh0+s076K8hZFg7dgJOy2HS46tytNX7AAbEzuQouQo3R0OGV//asG3POej3VQTyRTzKVoRDOO7cVGgNenI4AjfAjUJu+gcOzHqrAj5qr92TEZOZf45+pAk6p5nrfL42NBThO8GB3pXQr2/k74HpkFmVXcZJRB7RDSGfhsFCsnDFZ4N4mHQJWc1/u00z7oGzymPSDGQBEUjzIwGbjBLLDHxdCGKWuwdUq5hAH8Nk55HGOycou7ciBBXOl8E3iTaSxldqOkFLpvkMQ6G2i6dH/1ERKxx61LtQveetkGGMPaLRlsgrUGJNftuKNGEMMQgxn4JykppxHqW3KBlzNhpFUn3QELIctk5SoV12XUDVWi4yhd49F0QlbqfDbRN7ogXo12/SYhUEBS4Wa2uo/mtVKkAdo+GYLtgcggP25y+Qw/I5CenBMJtm2mVFi1b/9AwPaDQo+Yd4S7SrGPyhvcRJcSareIyCXIFSDq6j40qPxGclUv0MLHdwiqxcmmiCP9PQwSnKstCNPBx+IN91E6UfnyBYBhXOWFPZqyHG5OtdBfrx9pIa+0TtFiMBbVGUDidj5QkslyLOZ5Zhx+RMOz+47GpiSg9LaObfJdH4vRsbsZgufvt5hceGE6+lUn3zQzTcwPLaEsQv4HsNMEUKW+tt8K6ZB3GLWaWtII4g0gVlQi2T5P4ZsvBFXf2YJFj4cAL21JVcRanjD2vYk0SbYyuOM2fpBtMJR8pIbVTzdyEw3pPQdyHo4LlbYBFkM3DaXxum9qHr2MHFeAefwJRM79ou5laulfj4nqBPi6hhfT1Z9r9ToDPujOtH+0jRnTIPs4zAWY6rXzUivPJkcu3iqUsqCZcQaU5SHhKli/bHakINyRyTd1ozpdrMsE3rn2VorpgJyVDT47/Bh+xG0F8lCZKofgh4w7DTRxOcIJwkUaPqu30lHHbmc8q0JfGXOgTc496TyjdFx4R529DMzDDkSCVFKp3z8qnG46WsIOIGCp2OXvIiSIihyQFO3FnBx16NbdVlicnTwov4TUPRcsYDIx0p33c3hxOmn1RR1aygYx7XvG3tuMS8ktpfq12ENy3zwpeOit1b8ylnBHwEdaAENaVy03TOLMxIj8rZSfj2AXhnwAKPMLE1AWKufE7OkQAGg2JyJ/H5wB69k9FjwmG0UbkpDGCHNKTMSmwWC6ppV08g/VqUxP50cgXdk19u7Atr1AjCmDXdkFJXYeYwg==\",\"flatData\":\"\",\"keySize\":4096}";
Webhook webhook = new Webhook(secret);
webhook.verify(payload, headers);
// Lanza una excepción en caso de error, devuelve el contenido verificado en caso de éxito
}
}
Validating source IP addresses
In case your webhook receiving endpoint contains a firewall or NAT configuration, consider allowing traffic from the following list of IP addresses to receive notifications from Zenki.
- 54.216.8.72
- 54.173.54.49
- 52.215.16.239
- 52.55.123.25
- 52.6.93.106
- 63.33.109.123
- 44.228.126.217
- 50.112.21.217
- 52.24.126.164
- 54.148.139.208