Better Route WEBHOOKs
Using Bettermile Webhooks for real-time updates
Listen for events on your Bettermile tours, so your integration can automatically trigger reactions in your system.
The Bettermile Webhook API provides a way for Bettermile tenants to interact programmatically with Bettermile by subscribing to important events of your tours. Bettermile will notify your application whenever an important event happens.
Note: For tour set up and tour interaction processes via your backend, we recommend an integration of the Better Route API or the use of the Bettermile Driver App.
How does it work?
When your application endpoint is enabled on Bettermile to receive events, Bettermile will push real-time notifications to it. These notifications contain the type of event, such as updates to sequences on tours. On your side, you can then use that to integrate Bettermile with existing or new software.
Getting started with the Bettermile Webhook API
Preparing to receive a Webhook Notifications
As prerequisite of Bettermile enabling to send you Webhook notifications, you must make sure you are prepared to receive them:
- You will need to implement an HTTPS endpoint to receive POST requests as a JSON payload that include the event objects. (please share the endpoint URL with the Bettermile team)
- For each request received from Bettermile, your endpoint needs to answer with a 2xx status code.
- Bettermile will not transfer any data on unsafe connections. So please, ensure your created endpoint is both public, and can handle HTTPS connections. We accept endpoints with self-signed keys. (To be provided by you.)
Event types
You should only get from the events you receive the important fields for your logic to work. You also should ignore any events not important for your use case. Parsing or checking the data on fields and events you don't need may put undue stress on your side.
Delivery attempts
If your endpoint is down or not responding with a 2xx
code, Bettermile will keep retrying the delivery of the messages by around an hour. The retries have an exponential backoff independent for each message.
After the 1-hour period is over, Bettermile will stop trying to deliver the message and discard it. Due to the fast nature of delivery events we consider 1 hour to be enough time for the message to still be relevant and be retried.
Handling the Events
Due to the distributed nature of our internal systems, there may be some edge cases happening.
Out Of Order Delivery
The delivery of the messages to your endpoint may come out of order from the actual order the events themselves occurred. If ordering is important for your internal systems, you should set up a system to handle this case. We strongly recommend you don't do that though. It may put undue strain on your systems to order the messages you receive. We recommend you project a system as stateless as possible to prevent unforeseen edge-cases.
Duplicate Delivery
Some messages may be delivered more than once, though rare, this can happen. Thus, we recommend your systems are able to handle the received messages in an idempotent manner. One way to do this is storing the events you received and if you receive one that is already stored, you may discard it and answer with a 2xx
code.
Secure connections
Bettermile will outright refuse any non-encrypted connection. So be sure that HTTPS is enabled on your endpoint, even if with a self-signed key.
Redirects and other HTTP statuses
Bettermile will consider any status different from 2xx
statutes an error. This also means Bettermile will not follow any redirects, considering a redirect status an error, and will keep retrying until the status is 2xx
or the retry window expires.
Webhook Events
This document describes the common message structure all the Bettermile's webhook events share.
The message body
Any messages sent by Bettermile will have the following structure:
Ƭ EventSchema: Object
Example
{
"version":"0",
"id":"2a4650b3-e231-dc1b-2046-c7beb1ada720",
"time":"2023-10-05T09:47:07Z",
"source":"better_route",
"detail-type":"SEQUENCE_CHANGED",
"detail": {
"data based on the event type"
}
}
Type declaration
Name | Description |
---|---|
version |
The version for this event. Currently 0 (zero) for all events. |
id |
Specific event identifier |
time |
The time the event occurred. |
source |
This identifies the service that generated the event. Events can be generated in any Bettermile product out of the Bettermile Suite - the source giving you an orientation which Bettermile product the event originates from. |
detail-type |
The event type is describing the type of event being sent. For example when a sequence is updated detail-type will be SEQUENCE_CHANGED emitted. |
detail |
A JSON object that contains information about the event. |
Event types
The EventType, which defines what kind of data each event will have as well as what resource type the event happened upon.
Ƭ EventType: SEQUENCE_CHANGED
Description
SEQUENCE_CHANGED
: When there is an update to the Sequence of a tour. This is triggered by changes to the order of waypoints. (a quite common example is here a timeframe update by the driver on one of the waypoints and with this the waypoint moving within the sequence or also a pickup being added during the day)
SEQUENCE_CHANGED
Schema
Ƭ SequenceChanged: Object
The SEQUENCE_CHANGED
data model: All SEQUENCE_CHANGED
events in the API will have this format.
Description
A SEQUENCE_CHANGED
is an update to the order of waypoints on a Bettermile Tour.
Example
{
"version":"0",
"id":"2a4650b3-e231-dc1b-2046-c7beb1ada720",
"time":"2023-10-05T09:47:07Z",
"source":"better_route",
"detail-type":"SEQUENCE_CHANGED",
"detail":{
"tourId":"ac15b30a-23b4-45e4-ad2f-2250557b1d09",
"depot": "65",
"date":"2023-10-05",
"generatedAt":"2023-10-05T09:47:07Z",
"assignment":"4567",
"waypoints":[
{
"waypointId":"5ccf646c-658b-47f9-ae05-b44c5aa2466a",
"status":"UNPROCESSED",
"sequenceError":null,
"type":"GENERATED",
"jobs":[
{
"jobId":"1420db1a-a315-483b-bb2f-0bb03b1e3867",
"state":"UNPROCESSED",
"type":"DELIVERY",
"externalId":"112G1K336L15"
},
{
"jobId":"11ed0624-26cf-4b45-831c-2e28ece75133",
"state":"UNPROCESSED",
"type":"DELIVERY",
"externalId":"2H74B49A394"
},
{
"jobId":"7fa75f76-1357-4eec-9203-19aeb15a39de",
"state":"UNPROCESSED",
"type":"PICKUP",
"externalId":"7M21O67P173"
},
{
"jobId":"5a8cb64a-6c3d-438f-bf8c-f6025ac01de1",
"state":"UNPROCESSED",
"type":"PICKUP",
"externalId":"33H43R2W897"
},
],
"waypointId":"5ccf646c-890b-47f9-ae05-b33c5aa2477a",
"status":"UNPROCESSED",
"sequenceError":null,
"type":"GENERATED",
"jobs":[
{
"jobId":"1420db1a-a315-483b-bb2f-0bb03b1e3867",
"state":"UNPROCESSED",
"type":"DELIVERY",
"externalId":"6Q40P9K2L98"
},
]
},
],
"unassignedJobs":[
{
"jobId":"2a660a2a-bfb6-451e-b4f4-bca4976023bd",
"state":"PROCESSED",
"type":"DELIVERY",
"externalId":"9P5I44A887"
}
]
}
}
Type declaration
Name | Description |
---|---|
tourId |
Unique tour identifier generated by Bettermile |
depot |
Depot identifier |
date |
Date of an assignment being out for delivery |
generatedAt |
A time of the sequence update occurred |
assignment |
Vehicle / route / driver identifier |
waypoints |
The array of waypoints, that contain respectively the jobs – the order of waypoints will reflect the order of waypoints within the sequence |
waypoints waypointId |
A unique identifier of this waypoint - This is a stop on the tour where multiple jobs/services can be grouped in to. It is based on locality/address |
waypoints status |
State of the waypoint - Enum: "UNPROCESSED" , "PROCESSED" , "CONFIRMED" |
waypoints sequenceError |
It describes a reason that prevents this waypoint from being placed into the sequence. - Enum: "UNREACHABLE_TIMEFRAME" , "OUTSIDE_ROAD_NETWORK" , "EXCEEDS_MAX_DISTANCE_TO_DEPOT" , "UNMAPPED_REASON" - Default = null - UNREACHABLETIMEFRAME: The waypoint cannot be visited within the given timeframe OUTSIDEROADNETWORK: The waypoint cannot be accessed over road network EXCEEDSMAXDISTANCETODEPOT: The waypoint is too far away from tour's depot UNMAPPEDREASON: Some other undocumented reason. |
waypoints type |
Type of waypoint - Enum: "GENERATED" , "CUSTOM" |
waypoints jobs |
Array of jobs within a waypoint |
jobs jobId |
A unique identifier used for jobs within Bettermile |
jobs externalId |
The job id provided to Bettermile Data Gateway. (matches in most cases the customers parcel Id) |
jobs state |
Status of the job - Enum: "PROCESSED" , "UNPROCESSED" , "UNKNOWN" |
jobs type |
Type of job - Enum: "DELIVERY" , "PICKUP" , "COLLECTION" , "CUSTOM" , "UNKNOWN" - note: “custom” jobs do NOT have an external job id - hence can also not be mapped back to customers jobs |
unassignedJobs | Array of jobs not being part of a waypoint - this can be caused by e.g. missing coordinates on the job or missing address components. |
Some more context on Better Route
Important to know
SEQUENCE_CHANGED
events can only be generated when a tour was previously set up (e.g. by logging into the Bettermile Driver App to a tour) and updates to sequences are “requested”, this is only the case if e.g. the driver is being online with the app (in foreground) or sequence regeneration requests are sent for the tour through the public API.
SEQUENCE_CHANGED
events are generated with every change in the sequence - so when there is a change in the waypoint order (common examples are: a timeframe update by the driver on one of the waypoints or also a Pickup being added during the day)
How to sort the given jobs in a SEQUENCE_CHANGED
event best?
The event SEQUENCE_CHANGED
indeed gives quite some freedom for interpretation for sorting, but it will allow you to show the same order in jobs as in the Bettermile Driver App also on your driver’s hand scanners.
Please note: all sequences are a calculated status, not what the driver may actually have done while delivering.
Please consider these 4 groups of waypoints and jobs to sort or take into account for sorting:
- processed Waypoints (and their jobs) - in order
- unprocessed Waypoints (and their jobs) - in order
-
unprocessed Waypoints (and their jobs) with a
sequenceError
- not ordered - unassigned jobs (jobs not being part of a waypoint) - not ordered
It is up to you to either separate these out (as we do also in the Driver App) or to separate only sequence error and unassigned jobs out.
A recommendation is to sort all 4 as follows:
- all processed waypoints (and their jobs; regardless of these having a sequence error or not)
- all unassigned jobs
- all unprocessed waypoints (and their jobs; only with sequence errors)
- all unprocessed waypoints (and their jobs; only if without sequence errors)
We have a slight different order within the Bettermile Driver App - it makes sense though on the hand scanner to reflect problematic jobs quite high up (or during the tour in view), as with this the driver can react to these better (e.g. by adding or updating the address)
In need for another EventType?
In case you have further ideas or needs for Bettermile webhook events, please contact your Bettermile key account team or drop us an email to route@bettermile.com - we are just 1 hook away.