Webhooks

Webhooks are calls by the developer of an URL input using the HTTPS protocol, placed in the instant, when the e-shop encounters a specific event, for example the creation of a new order. Shoptet takes care of their delivery. The list of available events is in Apiary.

Example of webhook usage

First you need to register the webhook. This is done using the endpoint POST /api/webhooks. For example, if you want to be notified of new orders, please send:

{
  "data": [
    {
      "event": "order:create",
      "url": "https://myapplication.tld/new_order.php"
    }
  ]
}

API responds approximately as follows:

{
  "data": {
    "id": 123,
    "event": "order:create",
    "url": "https://myapplication.tld/new_order.php",
    "created": "2018-08-02T17:22:02+0200",
    "updated": null
  },
  "errors": null
}

From now on, whenever a new order is created at e-shop, Shoptet will invoke https://myapplication.tld/new_order.php as POST, with the following content:

{
    "eshopId": 222651,
    "event": "order:create",
    "eventCreated": "2019-01-08T15:13:39+0100",
    "eventInstance": "2018000057"
}

The event notification contains the e-shop identification (eshopId), event type (order:create = order creation), time of creation and the instance concerned (eventInstance) – in our case this is the order with number 2018000057.

Now, we are likely to be interested in the detail of this order – according to eshopId, we are looking for the correct Oath-Access-Token. We will ask for Access-Token and using the standard way, we will ask for an order using GET /api/orders/2018000057.

Webhooks registration

The above demonstrated how to register a new webhook. To do this, it is possible to register more than one URL post /api for a single event (e.g. order:update), but it must not be repeated for one event. URLs with 80, 8080, and 443 ports are permitted.

It is also important to remember that webhooks are registered for each installation (i.e. each e-shop and addon) separately. It is therefore not possible to register a single webhook for an addon, that would then send notifications from all e-shops that will install it. This gives you the flexibility to register webhooks only for those e-shops where needed (for example, the user switches off the notification) and you can use URLs specific to an e-shop (to make it easier to identify the e-shop), such as https://call.me/order/shopId134.

The registration or further modification of these „addon events“ is not subject to any approval process, they are valid immediately. These are fully controlled by the addon’s author.

Further endpoints for the webhooks‘ content

Registered webhooks are valid for one addon and one e-shop at a time. It is not possible to register or read webhooks for any other addon, even if it is from the same developer and for the same e-shop – it is linked to the specific installation of the addon.

Notifications

A notification sent to a webhook URL must be confirmed by the webhook, using HTTP code 200. Otherwise we will repeat the notification after 15 minutes, twice (2) (maximum total of 3 attempts). When the last notification has not been confirmed, we will mark the notification as inactive.

The notifications made on the webhooks are logged, including the number of repetitions, status, timestamps and whether they are active. The author of the addon has the possibility to read the logs of the implemented requests for webhooks using the endpoint GET /api/webhooks/notifications.

Notifications from Shoptet will only be sent from IP addresses 78.24.15.64/26.

The notification should be processed quickly by you, and you should execute only the most necessary actions. Otherwise there is an increased risk of an error occurring and the webhook will be called again or the time until which we expect a response will expire (4 seconds). Ideally, you should only save the identification of the change (for example „order/new/number“) and your own processing (reading the detail, internal processing, sending an e-mail, etc.) shall be done in an asynchronous manner.

In addition, the notifications may not to be sent one by one; they can go in parallel.

Signing the webhooks

To verify that the event notification was sent by Shoptet, and that the message was not changed en route, it is possible to use the Shoptet-Webhook-Signature header, which contains a hash of the data sent in the body of the notification.

You must first call the endpoint POST /api/webhooks/renew-signature-key, that generates the signatureKey. Save it.

Shoptet will save it for the addon and e-shop as well. When the event notice is issued, it calculates the hash as follows:

ShoptetWebhookSignature = hash_hmac('sha1', $messageBody, $signatureKey);

This checksum (hash) is inserted into the Shoptet-Webhook-Signature HTTP header before calling the specified URL.

It is recommended that the signature be verified as soon as the message is received. This means taking the body of the received message, creating a custom signature calculation using the signatureKey obtained above (using hash_hmac or sha1) and then verify it with the Shoptet-Webhook-Signature value delivered in the HTTP header.

Example of verification in PHP:

<?php
$webhookBody = '{"eshopId":315185,"event":"addon:uninstall","eventCreated":"2019-09-23T22:01:36+0200","eventInstance":"315185"}';
$signatureKey = '61d1175f54c47dd67df14c17002a17b2';
$calculated = hash_hmac('sha1', $webhookBody, $signatureKey);
$expected = 'a0e0a3e7689bd4c80e4d6ffcccb05235b864e1d0';

printf('calculated: %s %s', $calculated, PHP_EOL);
printf('expected: %s %s', $expected, PHP_EOL);
printf('is equal: %s %s', $calculated === $expected ? 'yes' : 'no', PHP_EOL);

Example of verification in Python:

import base64
import hmac
import hashlib

key = '61d1175f54c47dd67df14c17002a17b2'
message = '{"eshopId":315185,"event":"addon:uninstall","eventCreated":"2019-09-23T22:01:36+0200","eventInstance":"315185"}'
digest = hmac.new(key.encode('UTF-8'), message.encode('UTF-8'), hashlib.sha1).hexdigest();
expected = 'a0e0a3e7689bd4c80e4d6ffcccb05235b864e1d0'

print "calculated: {}".format(digest)
print "expected: {}".format(expected)
print "is equal: {}".format(digest == expected)