This article is for developers that want to programmatically push orders to a delivery operator that is powered by MotionTools.
Overview
To push an order to a MotionTools powered delivery operator you will make use of the MotionTools REST API. Additional resources & guides can be found in our Help Center.
MotionTools is a multi-tenant platform where each delivery operator has their own tenant. You will push orders to your delivery operator via an account with the customer role.
If you know the url to the Customer Portal of your delivery operator, then you can create a customer account yourself.
Alternatively, please contact your delivery operator to obtain a customer account.
You can push orders by creating either Bookings or Packages via the API. When you create a booking or a package you will receive as response that represents your order in MotionTools. Learn more with our in-depth articles about Bookings and Packages. If you are unsure about which option to go with, please read this article and additionally discuss this with your contact at your designated operator / 3PL. This document describes integrating bookings, which is the default way when you are requesting orders to be delivered within 60 minutes or less and the transport goes straight from the pickup to the drop-off, without touching a hub or warehouse in between.
You can track the progress of your order by listening to Webhooks that are relevant to your use-case. Complete list Here ↗️. Setting up webhooks cannot be done directly in your customer account, but needs assistance from your contact at your provider.
Scheduling modes: Instant vs Scheduled
When you create a booking you can specify a when the delivery operator is expected to show up at the first stop, which usually is the pickup stop.
Currently two options are available: instant & scheduled bookings:
Instant bookings: indicate that the driver should come to the first stop now or as soon as possible
Scheduled bookings: indicate that the arrival at the first stop should be sometime in the future, known as the
scheduled_attime.
More details here.
Time windows
MotionTools allows you to specify when exactly a driver should arrive on each individual stop of the booking (earliest_arrival_at, latest_arrival_at). Additionally you can specify the expected duration of a stop(custom_expected_duration_seconds). Refer to the Create Booking Endpoint request payload for details, in the booking.stops model.
Booking lifecycle
It’s important to understand the main concepts in MotionTools that are involved in the booking lifecycle: Booking, Stop, Tour.
Let's go through the flow and the relevant webhook events to listen to:
Create a booking using in the
customerrole with your preferred scheduling mode (booking.created)Once a booking was dispatched into a tour and a driver started that tour, we trigger the
booking.in_progresseventOnce the driver heads to a specific stops, e.g. pickup stop or drop-off stop, there are events for arriving (
booking.stop_arrived) and completing (booking.stop_completed) such stopsOnce all stops of a booking are completed, the booking itself will be operationally completed as well (part of
booking.transition)
To learn more about the Booking status model please read: Status model of Bookings
The relevant webhooks & their payload are described in more detail in the Receiving updates via Webhooks section.
Add your order ID as external ID onto a booking
In order to establish a connection between various properties on your internal order model (e.g. order id) and the resulting booking in MotionTools, please add your own ID onto the booking as external_id. By doing this, your order id can be easily used to find the corresponding booking in MotionTools and you will also get this as part of the updates we sent in case you subscribe to webhooks. That way you can easily link back updates to your transaction/order in your system
Integration prerequisites
In order to integrate with your delivery operator powered by MotionTools, you need to have the following available/configured:
Customer account: on the MotionTools Tenant of your delivery operator
Customer API token: Once you have access to your account, you need to create an API token.
service_id: You can optionally set it when creating a booking in case the operator offers multiple delivery services and you want to book a specific one (e.g. express vs. same day). More about services here.
Push an order via API
Please read Customer: Create a Booking via API
API endpoint docs: CreateHailingBooking
Sample payload for a pickup, customized to food delivery (expand to view)
Sample payload for a pickup, customized to food delivery (expand to view)
{ "booking": { "stops": [ { "type": "pickup", "earliest_arrival_at": null, "latest_arrival_at": null, "place_id": "7d6991cb-d669-40ae-acb7-6a2b964e212b", // add a valid place id for the pickup location "additional_information": "info for pickup, e.g. order id or name", "metadata": { "public_order_id": "value", "external_order_id": "value" }, }, { "type": "dropoff", "earliest_arrival_at": null, "latest_arrival_at": null, "lat": 53.54439, // optional, MotionTools will geocode if n/a "lng": 9.940334, "street": "Große Elbstraße", "number": "123", "zip_code": "20097", "city": "Hamburg", "country": "Deutschland", "location_name": "Große Elbstraße 123", "first_name": "Vorname", "last_name": "Nachname", "phone_number": "+491601234123", "additional_information": "Please don't ring the bell", "metadata": { "public_order_id": "value", "external_order_id": "value" }, } ], "scheduled_at": "now" // if you only push bookings for now, you can leave this out, otherwise you can add a timestamp, please note that only timestamps in the future are allowed and they have to be within the opening hours of the operator. Scheduled_at is typically the time when you want a driver to pickup the order from the restaurant. } }Set the contact details of the final recipient of the order as Stop Contact Details on the dropoff stop in order to allow drivers/couriers from your delivery partner to contact the final recipient, when they’re processing the booking.
Receiving updates via Webhooks
Setting up webhooks can only be done by admin users.
In order to create a webhook, you need to share the URL that you want to receive webhooks to with either a representative of your delivery partner (as they should have admin access), or with a MotionTools support. You also need to specify which events you want receive and potentially which filters to apply.
General information
MotionTools can send status updates via webhooks for almost any event that happens within MotionTools
The webhooks are lean and only include the most relevant information, such as the event that happened and the booking id that it is about
If you want to get more detailed information, you have to fetch the information from the relevant endpoint after receiving a webhook that requires you to get more details, e.g. the “show booking” endpoint
The API docs for webhooks can be found here: https://docs.motiontools.io/#tag/Webhooks
To ensure that your webhook consumer endpoint is invoked by authentic requests from MotionTools, we added support for webhook request verification. See: Webhook verification
Location tracking & ETAs
Driver location updates
Fetch driver details via ShowBookingStopsDriverContactDetails endpoint
Up to date ETAs
Action: fetch details via EstimateBookingStopsArrivals
Related Help center article: Integrating ETA services via API
Webhook verification
To verify that an incoming webhook request was not maliciously forged or modified we include a signature as a part of the request headers. The signature must be validated on your side against the public key you obtained from your delivery partner when they configured the webhook.
Ed25519 is used as the encryption algorithm, and the signature is given Base64-encoded in the X-Mtools-Signature request header.
Cloudlfare Worker TypeScript sample for verifying webhook authenticity
Cloudlfare Worker TypeScript sample for verifying webhook authenticity
💡Note: The sample below is suitable for the Cloudlfare Workers/Deno runtime. If you’re using a different JavaScript runtime such as NodeJs or Bun then the below example will not work out of the box. For example, in NodeJs use Ed25519 rather than NODE-ED25519 to identify the algorithm. Changes might also exist when interacting with the crypto library.
The code below only servers as an example. Please adjust accordingly to match the requirements for your runtime.
// Replace this with your actual public key (Base64-encoded)
const PUBLIC_KEY_BASE64 = 'YOUR_PUBLIC_KEY_HERE';
export default {
async fetch(request: Request): Promise<Response> {
// Read body as ArrayBuffer (Uint8Array required for crypto.subtle)
const body = new Uint8Array(await request.arrayBuffer());
// Get signature from header (Base64-encoded)
const signatureBase64 = request.headers.get('X-Mtools-Signature');
if (!signatureBase64) {
return new Response('Missing signature header', { status: 400 });
}
const signature = Uint8Array.from(atob(signatureBase64), c => c.charCodeAt(0));
// Decode public key
const publicKey = Uint8Array.from(atob(PUBLIC_KEY_BASE64), c => c.charCodeAt(0));
// Import public key for Ed25519 verify
const cryptoKey = await crypto.subtle.importKey(
'raw',
publicKey,
{ name: 'NODE-ED25519', namedCurve: 'NODE-ED25519' }, // For Deno/Cloudflare
false,
['verify']
);
// Verify signature
const isValid = await crypto.subtle.verify(
'NODE-ED25519',
cryptoKey,
signature,
body
);
if (!isValid) {
return new Response('Invalid signature', { status: 401 });
}
// Signature valid, process webhook
try {
await procesWebhook(body)
return new Response('Webhook verified!', { status: 200 });
} catch (e) {
return new Response('Internal server error: error processing webhook.', { status: 500 });
}
}
};
Webhook consumption considerations
To ensure performance stability for our platform, we impose the following limits on webhook consumers:
We expect a valid
2xxresponse code to consider successful the delivery of any particular webhook event.15seconds timeout: if no
2xxresponse in 15 seconds we consider the delivery failed.3 retries: each webhook event is retried 3 times. After 3 attempts, the webhook event is never re-sent again and essentially lost.
250 consecutive failed deliveries to the same URL will block/deactivate the webhook and send an email notification to the tenant owner (in this case, your delivery partner). If this should happen, either you delivery partner or MotionTools support will contact you.
Webhook Filtering
If you receive webhook events for bookings that do not belong to you, then perhaps your delivery partner did not correctly set up the Webhook Filters when they configured the webhook for you.
Please contact your delivery partner to adjust this. Relevant article in our help center: Forward Webhooks to your customers for Booking status updates.
Technical considerations & recommendations
Webhook considerations
Data validation
Our API & payload structures & status models are constantly evolving.
As such, we advise:
against strictly validating payload structure → your integration should not crash if new properties appear in a MotionTools payload you consume
against strictly validating known keyword values as enums (e.g. statuses, transitions, other keyword values such as
nowfor thebooking.scheduled_atproperty) → your integration should be able to deal bug-free and without crashes with new, unknown statuses, transitions, etc.
Testing your integration
For testing purposes you will get access to your own MotionTools tenant that will closely match the tenant of your delivery partner. This tenant will act as your sandbox environment where you can test, iterate and experiment while developing your integration.
However, bear in mind that on this development sandbox tenant, you will have access to the tenant admin user, but in production you will only have access to a customer user on your delivery partner’s production tenant.
As such, please make sure that on your sandbox environment you also create a customer account and that you use that account’s API key (i.e. key with the customer role) while developing your integration on your sandbox tenant.
