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

Copy
Copied
{
    "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

Copy
Copied
{
    "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:

  1. all processed waypoints (and their jobs; regardless of these having a sequence error or not)
  2. all unassigned jobs
  3. all unprocessed waypoints (and their jobs; only with sequence errors)
  4. 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.