Creating a Webhook
This guide will walk you through creating a Webhook using the Montonio API. More info can be found in the API reference.
Creating a Webhook
As the Montonio API utilizes asynchronous processing, itβs essential to register a webhook to receive notifications when certain events happen. Ideally, you should register for all the events described in the table below:
| Event | Description |
|---|---|
shipment.registered | Shipment was successfully registered. The label file can be generated for this shipment. |
shipment.registrationFailed | We failed to register the shipment with the carrier. Usually, this means that shipment data is incorrect. |
shipment.statusUpdated | Shipment delivery status is updated. The first status update notification is sent when the carrier has picked up the shipment, and it goes into status inTransit. |
shipment.labelsCreated | Carrier labels have been created for the shipment. The shipment status changes to labelsCreated. |
labelFile.ready | Label file is successfully generated. You can get the URL for the PDF file by calling the GET /label-file endpoint. |
labelFile.creationFailed | Something went wrong with generating the label file. |
Best Practice: Use a Single Webhook URL
| Approach | Webhooks Used | Recommendation |
|---|---|---|
| One webhook, multiple events | 1 | Recommended |
| Separate webhook per event | 5-6 | Avoid |
Benefits of a single webhook:
- Simpler management: One URL to update if your endpoint changes
- Preserves quota: You have a limit of 10 webhooks per store
- Easier debugging: All events route to one place
- Event correlation: Related events arrive at the same endpoint
Handling Multiple Event Types
Your webhook endpoint should inspect the eventType field in the decoded JWT payload to determine how to process each notification:
const decoded = jwt.verify(payload, secretKey);
switch (decoded.eventType) { case 'shipment.registered': // Handle successful registration break; case 'shipment.registrationFailed': // Handle registration failure break; case 'shipment.labelsCreated': // Handle labels created break; case 'labelFile.ready': // Handle label file ready break; case 'labelFile.creationFailed': // Handle label file creation failure break; case 'shipment.statusUpdated': // Handle status updates break;}Complete example
This is a complete example of creating a Webhook. Before continuing, make sure you have the necessary tools if you need to test webhook notifications locally. You can find more info about testing webhooks locally below.
The process of creating a webhook is as follows:
- Prepare a notification URL that can accept POST requests.
- Consider the workflow and requirements for your application and decide which events are important to you.
- Prepare the request data and make a POST request to the /webhooks endpoint.
Notes:
- You can register up to 10 webhooks per store. We will send a notification to each registered webhook.
- You can get all webhooks by calling the
GET /webhooksendpoint. - You can delete a webhook if you no longer need it by calling
DELETE /webhooks/:id.
import axios from 'axios';
const data = JSON.stringify({ "url": "https://webhook.site/305802f7-4bad-4401-b4ee-b4d89aeae6d2", "enabledEvents": [ "shipment.registered", "shipment.registrationFailed", "shipment.labelsCreated", "shipment.statusUpdated", "labelFile.ready", "labelFile.creationFailed" ]});
const config = { method: 'post', url: 'https://shipping.montonio.com/api/v2/webhooks', headers: { 'Content-Type': 'application/json', Authorization: 'Bearer [your_token]', }, data : data};
async function makeRequest() { try { const response = await axios.request(config); console.log(JSON.stringify(response.data)); } catch (error) { console.log(error); }}
makeRequest();The example API response will be the following:
{ "id": "3f922a3a-5063-405b-a489-a2a13a86a13b", "createdAt": "2024-06-13T10:41:22.191Z", "url": "https://webhook.site/305802f7-4bad-4401-b4ee-b4d89aeae6d2", "enabledEvents": [ "shipment.registered", "shipment.registrationFailed", "shipment.labelsCreated", "shipment.statusUpdated", "labelFile.ready", "labelFile.creationFailed" ]}Some errors which might occur during the request are listed below:
| Code | Description |
|---|---|
400 | Bad request. Please double-check the request body. You will get a more detailed error message. |
401 | Unauthorized. Please check if the JWT was generated correctly and the accessKey and secretKey are correct. |
500 | Internal server error. Something went wrong on our side. |
Webhook notification
We will send a POST request to the URL parameter of the created webhook. The request body will contain a payload property. The value of the payload is a JWT that contains the notification data.
Example JSON body of a webhook notification:
{ "payload": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJldmVudElkIjoiZTFiZTgxZmItMjM1NS00NGIyLTlmMjgtMmIxYTY5MTE1MWJiIiwic2hpcG1lbnRJZCI6Ijg3ZjU1MTQ3LTc3NjUtNGViNi05YmIxLTNjMWE0YjA1YTQzNSIsImNyZWF0ZWQiOiIyMDI0LTA2LTEzVDEwOjUxOjU3LjMyMloiLCJkYXRhIjp7ImlkIjoiODdmNTUxNDctNzc2NS00ZWI2LTliYjEtM2MxYTRiMDVhNDM1IiwiY3JlYXRlZEF0IjoiMjAyNC0wNi0xM1QxMDo1MTo1NS4yODhaIiwic3RhdHVzIjoicmVnaXN0ZXJlZCIsIm1vbnRvbmlvT3JkZXJJZCI6bnVsbCwib3JkZXJJZCI6Im9yZGVyIDEiLCJjYXJyaWVyU2hpcG1lbnRJZCI6bnVsbCwic2hpcHBpbmdNZXRob2QiOnsidHlwZSI6InBpY2t1cFBvaW50IiwiaWQiOiIzNzdjM2IwNi0wOTY3LTRmZjItYjI4YS0zNzJjYWIyMzQ4OTgiLCJjYXJyaWVyQ29kZSI6Im9tbml2YSIsImNvdW50cnlDb2RlIjoiRUUifSwic2VuZGVyIjp7ImlkIjoiMjgwZDY1ZTktZTliYy00ZTQxLWFjMTgtMTUwNGYxNTcyNDAwIiwibmFtZSI6IlNlbmRlciBZIiwiY29tcGFueU5hbWUiOiJDb21wYW55IFkiLCJzdHJlZXRBZGRyZXNzIjoiS2FpIDEiLCJsb2NhbGl0eSI6IlRhbGxpbm4iLCJyZWdpb24iOiJIYXJqdW1hYSIsInBvc3RhbENvZGUiOiIxMDExMSIsImNvdW50cnkiOiJFRSIsInBob25lQ291bnRyeUNvZGUiOiIzNzIiLCJwaG9uZU51bWJlciI6IjUzMzM0NzcwIiwiZW1haWwiOiJzdXBwb3J0QG1vbnRvbmlvLmNvbSJ9LCJyZWNlaXZlciI6eyJpZCI6ImE4YzQzMTgwLTU2YTgtNDJjNC1iZWM5LTdjNTBhZjdlMDY4NCIsImZpcnN0TmFtZSI6bnVsbCwibGFzdE5hbWUiOm51bGwsIm5hbWUiOiJSZWNlaXZlciBYIiwiY29tcGFueU5hbWUiOiJDb21wYW55IFgiLCJzdHJlZXRBZGRyZXNzIjoiS2FpIDExIiwibG9jYWxpdHkiOiJUYWxsaW5uIiwicmVnaW9uIjoiSGFyanVtYWEiLCJwb3N0YWxDb2RlIjoiMTAxMTEiLCJjb3VudHJ5IjoiRUUiLCJwaG9uZUNvdW50cnlDb2RlIjoiMzcyIiwicGhvbmVOdW1iZXIiOiI1MzMzNDc3MCIsImVtYWlsIjoic3VwcG9ydEBtb250b25pby5jb20ifSwicGFyY2VscyI6W3siaWQiOiIwYjEwYzFlMS1iZWFhLTRiMDktOWJiNi1jNmRkNDAwMjExNGQiLCJ3ZWlnaHQiOjEsImxlbmd0aCI6bnVsbCwiaGVpZ2h0IjpudWxsLCJ3aWR0aCI6bnVsbCwiY2FycmllclBhcmNlbElkIjoiQ0M1NDg5MzYzNDFFRSIsInRyYWNraW5nTGluayI6Imh0dHBzOi8vbWludS5vbW5pdmEuZWUvdHJhY2svQ0M1NDg5MzYzNDFFRT9sYW5ndWFnZT1ldCJ9XSwic3RvcmUiOnsiaWQiOiIwODhhZTQwOS1hZTI0LTRhM2MtYTY0MC01YzI2OWY3MzJjYWEifX0sImV2ZW50VHlwZSI6InNoaXBtZW50LnJlZ2lzdGVyZWQiLCJpYXQiOjE3MTgyNzU5MTcsImV4cCI6MTcxODg4MDcxN30.LaHCA4w6WHjQBnVMd0m8jIy7U7A2i2snnxivLYKkBCY"}The JWT contains an event object and a data property containing information about the specific event. You need to decode the JWT to access the data. Here is an example of a decoded token:
{ "eventId": "e1be81fb-2355-44b2-9f28-2b1a691151bb", "shipmentId": "87f55147-7765-4eb6-9bb1-3c1a4b05a435", "created": "2024-06-13T10:51:57.322Z", "data": { "id": "87f55147-7765-4eb6-9bb1-3c1a4b05a435", "createdAt": "2024-06-13T10:51:55.288Z", "status": "registered", "montonioOrderUuid": null, "merchantReference": "order 1", "carrierShipmentId": null, "shippingMethod": { "type": "pickupPoint", "id": "377c3b06-0967-4ff2-b28a-372cab234898", "carrierCode": "omniva", "countryCode": "EE" }, "sender": { "id": "280d65e9-e9bc-4e41-ac18-1504f1572400", "name": "Sender Y", "companyName": "Company Y", "streetAddress": "Kai 1", "locality": "Tallinn", "region": "Harjumaa", "postalCode": "10111", "country": "EE", "phoneCountryCode": "372", "phoneNumber": "53334770", "email": "support@montonio.com" }, "receiver": { "id": "a8c43180-56a8-42c4-bec9-7c50af7e0684", "firstName": null, "lastName": null, "name": "Receiver X", "companyName": "Company X", "streetAddress": "Kai 11", "locality": "Tallinn", "region": "Harjumaa", "postalCode": "10111", "country": "EE", "phoneCountryCode": "372", "phoneNumber": "53334770", "email": "support@montonio.com" }, "parcels": [ { "id": "0b10c1e1-beaa-4b09-9bb6-c6dd4002114d", "weight": 1, "length": null, "height": null, "width": null, "carrierParcelId": "CC548936341EE", "trackingLink": "https://minu.omniva.ee/track/CC548936341EE?language=et" } ], "store": { "id": "088ae409-ae24-4a3c-a640-5c269f732caa" }, "products": null }, "eventType": "shipment.registered", "iat": 1718275917, "exp": 1718880717}The token is signed with your Secret Key and needs to be validated.
See the example below for validating the token:
/* * We recommend using the jsonwebtoken package to verify * Json Web Tokens. You can install it with npm: * > npm install jsonwebtoken * More information can be found at * https://www.npmjs.com/package/jsonwebtoken*/import jwt from 'jsonwebtoken';
// the value of the payload property of the request body, which is a JWTconst payload = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJldmVudElkIjoiZTFiZTgxZmItMjM1NS00NGIyLTlmMjgtMmIxYTY5MTE1MWJiIiwic2hpcG1lbnRJZCI6Ijg3ZjU1MTQ3LTc3NjUtNGViNi05YmIxLTNjMWE0YjA1YTQzNSIsImNyZWF0ZWQiOiIyMDI0LTA2LTEzVDEwOjUxOjU3LjMyMloiLCJkYXRhIjp7ImlkIjoiODdmNTUxNDctNzc2NS00ZWI2LTliYjEtM2MxYTRiMDVhNDM1IiwiY3JlYXRlZEF0IjoiMjAyNC0wNi0xM1QxMDo1MTo1NS4yODhaIiwic3RhdHVzIjoicmVnaXN0ZXJlZCIsIm1vbnRvbmlvT3JkZXJJZCI6bnVsbCwib3JkZXJJZCI6Im9yZGVyIDEiLCJjYXJyaWVyU2hpcG1lbnRJZCI6bnVsbCwic2hpcHBpbmdNZXRob2QiOnsidHlwZSI6InBpY2t1cFBvaW50IiwiaWQiOiIzNzdjM2IwNi0wOTY3LTRmZjItYjI4YS0zNzJjYWIyMzQ4OTgiLCJjYXJyaWVyQ29kZSI6Im9tbml2YSIsImNvdW50cnlDb2RlIjoiRUUifSwic2VuZGVyIjp7ImlkIjoiMjgwZDY1ZTktZTliYy00ZTQxLWFjMTgtMTUwNGYxNTcyNDAwIiwibmFtZSI6IlNlbmRlciBZIiwiY29tcGFueU5hbWUiOiJDb21wYW55IFkiLCJzdHJlZXRBZGRyZXNzIjoiS2FpIDEiLCJsb2NhbGl0eSI6IlRhbGxpbm4iLCJyZWdpb24iOiJIYXJqdW1hYSIsInBvc3RhbENvZGUiOiIxMDExMSIsImNvdW50cnkiOiJFRSIsInBob25lQ291bnRyeUNvZGUiOiIzNzIiLCJwaG9uZU51bWJlciI6IjUzMzM0NzcwIiwiZW1haWwiOiJzdXBwb3J0QG1vbnRvbmlvLmNvbSJ9LCJyZWNlaXZlciI6eyJpZCI6ImE4YzQzMTgwLTU2YTgtNDJjNC1iZWM5LTdjNTBhZjdlMDY4NCIsImZpcnN0TmFtZSI6bnVsbCwibGFzdE5hbWUiOm51bGwsIm5hbWUiOiJSZWNlaXZlciBYIiwiY29tcGFueU5hbWUiOiJDb21wYW55IFgiLCJzdHJlZXRBZGRyZXNzIjoiS2FpIDExIiwibG9jYWxpdHkiOiJUYWxsaW5uIiwicmVnaW9uIjoiSGFyanVtYWEiLCJwb3N0YWxDb2RlIjoiMTAxMTEiLCJjb3VudHJ5IjoiRUUiLCJwaG9uZUNvdW50cnlDb2RlIjoiMzcyIiwicGhvbmVOdW1iZXIiOiI1MzMzNDc3MCIsImVtYWlsIjoic3VwcG9ydEBtb250b25pby5jb20ifSwicGFyY2VscyI6W3siaWQiOiIwYjEwYzFlMS1iZWFhLTRiMDktOWJiNi1jNmRkNDAwMjExNGQiLCJ3ZWlnaHQiOjEsImxlbmd0aCI6bnVsbCwiaGVpZ2h0IjpudWxsLCJ3aWR0aCI6bnVsbCwiY2FycmllclBhcmNlbElkIjoiQ0M1NDg5MzYzNDFFRSIsInRyYWNraW5nTGluayI6Imh0dHBzOi8vbWludS5vbW5pdmEuZWUvdHJhY2svQ0M1NDg5MzYzNDFFRT9sYW5ndWFnZT1ldCJ9XSwic3RvcmUiOnsiaWQiOiIwODhhZTQwOS1hZTI0LTRhM2MtYTY0MC01YzI2OWY3MzJjYWEifX0sImV2ZW50VHlwZSI6InNoaXBtZW50LnJlZ2lzdGVyZWQiLCJpYXQiOjE3MTgyNzU5MTcsImV4cCI6MTcxODg4MDcxN30.LaHCA4w6WHjQBnVMd0m8jIy7U7A2i2snnxivLYKkBCY';
// Use your secret key to verify the tokenconst decoded = jwt.verify(payload, 'MY_SECRET_KEY'); // can throw
if (decoded.data.status === 'registered') { // shipment registered} else if (decoded.data.status === 'registrationFailed') { // shipment not registered, check the reason}π οΈ Testing locally
There are a few different options for testing webhooks locally, but the easiest one is ngrok to expose your local server to the internet or webhook.site for quick troubleshooting.
We have written a more detailed guide in our Help Center. Have a look at it if you need more information.