Skip to content

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:

EventDescription
shipment.registeredShipment was successfully registered. The label file can be generated for this shipment.
shipment.registrationFailedWe failed to register the shipment with the carrier. Usually, this means that shipment data is incorrect.
shipment.statusUpdatedShipment 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.labelsCreatedCarrier labels have been created for the shipment. The shipment status changes to labelsCreated.
labelFile.readyLabel file is successfully generated. You can get the URL for the PDF file by calling the GET /label-file endpoint.
labelFile.creationFailedSomething went wrong with generating the label file.

Best Practice: Use a Single Webhook URL

ApproachWebhooks UsedRecommendation
One webhook, multiple events1Recommended
Separate webhook per event5-6Avoid

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:

  1. Prepare a notification URL that can accept POST requests.
  2. Consider the workflow and requirements for your application and decide which events are important to you.
  3. 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 /webhooks endpoint.
  • 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:

CodeDescription
400Bad request. Please double-check the request body. You will get a more detailed error message.
401Unauthorized. Please check if the JWT was generated correctly and the accessKey and secretKey are correct.
500Internal 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 JWT
const payload = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJldmVudElkIjoiZTFiZTgxZmItMjM1NS00NGIyLTlmMjgtMmIxYTY5MTE1MWJiIiwic2hpcG1lbnRJZCI6Ijg3ZjU1MTQ3LTc3NjUtNGViNi05YmIxLTNjMWE0YjA1YTQzNSIsImNyZWF0ZWQiOiIyMDI0LTA2LTEzVDEwOjUxOjU3LjMyMloiLCJkYXRhIjp7ImlkIjoiODdmNTUxNDctNzc2NS00ZWI2LTliYjEtM2MxYTRiMDVhNDM1IiwiY3JlYXRlZEF0IjoiMjAyNC0wNi0xM1QxMDo1MTo1NS4yODhaIiwic3RhdHVzIjoicmVnaXN0ZXJlZCIsIm1vbnRvbmlvT3JkZXJJZCI6bnVsbCwib3JkZXJJZCI6Im9yZGVyIDEiLCJjYXJyaWVyU2hpcG1lbnRJZCI6bnVsbCwic2hpcHBpbmdNZXRob2QiOnsidHlwZSI6InBpY2t1cFBvaW50IiwiaWQiOiIzNzdjM2IwNi0wOTY3LTRmZjItYjI4YS0zNzJjYWIyMzQ4OTgiLCJjYXJyaWVyQ29kZSI6Im9tbml2YSIsImNvdW50cnlDb2RlIjoiRUUifSwic2VuZGVyIjp7ImlkIjoiMjgwZDY1ZTktZTliYy00ZTQxLWFjMTgtMTUwNGYxNTcyNDAwIiwibmFtZSI6IlNlbmRlciBZIiwiY29tcGFueU5hbWUiOiJDb21wYW55IFkiLCJzdHJlZXRBZGRyZXNzIjoiS2FpIDEiLCJsb2NhbGl0eSI6IlRhbGxpbm4iLCJyZWdpb24iOiJIYXJqdW1hYSIsInBvc3RhbENvZGUiOiIxMDExMSIsImNvdW50cnkiOiJFRSIsInBob25lQ291bnRyeUNvZGUiOiIzNzIiLCJwaG9uZU51bWJlciI6IjUzMzM0NzcwIiwiZW1haWwiOiJzdXBwb3J0QG1vbnRvbmlvLmNvbSJ9LCJyZWNlaXZlciI6eyJpZCI6ImE4YzQzMTgwLTU2YTgtNDJjNC1iZWM5LTdjNTBhZjdlMDY4NCIsImZpcnN0TmFtZSI6bnVsbCwibGFzdE5hbWUiOm51bGwsIm5hbWUiOiJSZWNlaXZlciBYIiwiY29tcGFueU5hbWUiOiJDb21wYW55IFgiLCJzdHJlZXRBZGRyZXNzIjoiS2FpIDExIiwibG9jYWxpdHkiOiJUYWxsaW5uIiwicmVnaW9uIjoiSGFyanVtYWEiLCJwb3N0YWxDb2RlIjoiMTAxMTEiLCJjb3VudHJ5IjoiRUUiLCJwaG9uZUNvdW50cnlDb2RlIjoiMzcyIiwicGhvbmVOdW1iZXIiOiI1MzMzNDc3MCIsImVtYWlsIjoic3VwcG9ydEBtb250b25pby5jb20ifSwicGFyY2VscyI6W3siaWQiOiIwYjEwYzFlMS1iZWFhLTRiMDktOWJiNi1jNmRkNDAwMjExNGQiLCJ3ZWlnaHQiOjEsImxlbmd0aCI6bnVsbCwiaGVpZ2h0IjpudWxsLCJ3aWR0aCI6bnVsbCwiY2FycmllclBhcmNlbElkIjoiQ0M1NDg5MzYzNDFFRSIsInRyYWNraW5nTGluayI6Imh0dHBzOi8vbWludS5vbW5pdmEuZWUvdHJhY2svQ0M1NDg5MzYzNDFFRT9sYW5ndWFnZT1ldCJ9XSwic3RvcmUiOnsiaWQiOiIwODhhZTQwOS1hZTI0LTRhM2MtYTY0MC01YzI2OWY3MzJjYWEifX0sImV2ZW50VHlwZSI6InNoaXBtZW50LnJlZ2lzdGVyZWQiLCJpYXQiOjE3MTgyNzU5MTcsImV4cCI6MTcxODg4MDcxN30.LaHCA4w6WHjQBnVMd0m8jIy7U7A2i2snnxivLYKkBCY';
// Use your secret key to verify the token
const 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.