Webhook integration
  • 21 Jun 2023
  • Dark
    Light

Webhook integration

  • Dark
    Light

Article summary

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:

  1. Add the url of your endpoint, optionally, you can attach a description of your endpoint.
  2. Select the event that will be notified to the endpoint in the Message Filtering option, you must choose at least one event to report.
  3. Optionally, you can configure the rate limit, in the Advanced Configuration section.

❗Only configuring the URL with HTTPS protocol is allowed.

🔒 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?

Check the details of the notifications sent to the webhook in the next link.

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.

💡 It is necessary to use the raw request body when checking webhooks, as the cryptographic signature is sensitive to even the slightest changes. You should be careful with frameworks that parse the request as JSON because this will also break the signature verification.

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



Was this article helpful?

What's Next