githubEdit

Custom carrier

You can create a custom carrier that is connected through an external service or operational process. This mechanism enables you to integrate:

  • Local delivery partners (for example, bike couriers)

  • Store operators who hand parcels to local drop-off points at flexible times

  • Internal delivery fleets with independent routing systems and apps

This configuration is handled through the Custom Carrier APIarrow-up-right. Each custom carrier requires a unique key that begins with CUSTOM_ (for example, CUSTOM_BOSSONBIKE). This ensures uniqueness and simplifies operational identification. The key must be unique within the tenant. Reusing a key is not allowed.

Custom carriers must be connected to facilities to be considered during routing and shipment creation.

Multiple custom carriers

While it's not possible to have more than one provided carrier in fulfillmenttools, it's possible to configure multiple custom carriers. Since the carrier key is used for identification and thus must be unique, a custom carrier must have the key starting with CUSTOM_ followed by any appendix (e.g., CUSTOM_BOSSONBIKE) to ease identification.

Custom Carriers represent carriers that are not natively supported by fulfillmenttools, but are connected by an external service or operative process.

While it is not possible to have more than one provided carrier in fulfillmenttools, it is possible to configure multiple custom carriers. Since the carrier key is used for identification and thus must be unique, a custom carrier must have a key starting with CUSTOM_ followed by an appendix (e.g., CUSTOM_BOSSONBIKE) to ease identification.

Use case: Headless carrier connection

A custom carrier enables a proprietary carrier integration with the fulfillmenttools platform. This tutorial showcases a possible solution for integrating an unsupported carrier, detailing the required processes and clients.

The integration model allows for a headless connection by using fulfillmenttools eventing and API interactions. This enables an external service, implemented and operated by a partner or customer, to manage the complexities of requesting a shipping label from an arbitrary carrier.

Concept

The following diagram and explanation describe the overall approach.

Drawing
A flowchart illustrating the five steps of the custom carrier integration concept, from event subscription in fulfillmenttools to setting handover states.
  1. Subscribe to a fulfillmenttools event. The process is triggered by an event subscription. The specific event chosen depends on the customer's process, but a logical trigger would be an event that occurs after packing is complete, such as PACK_JOB_CREATED. fulfillmenttools sends this event to the external service that handles the label request from the Carrier API.

  2. The external service gathers data and makes decisions. The external service receives the event and uses its payload to make decisions, such as selecting the exact carrier or carrier product, determining the necessary insurance value, etc. This data can be sourced from other services, a database, or fetched from fulfillmenttools via its API.

  3. Request a label from the Carrier API. Similar to the native fulfillmenttools mechanism, the external service uses the gathered data to request a label and negotiate a pickup with the carrier. The service receives the label and other data (e.g., pickup dates, transit times) from the carrier.

  4. Store the carrier label and data in fulfillmenttools. Once the label, track-and-trace information, and any additional data are received, the external service uploads this data to fulfillmenttools for seamless process integration. For example, the label is uploaded to a documentset, and the track-and-trace data is stored as a result on the parcel entity.

  5. Set handover states based on track-and-trace data. fulfillmenttools can set the handover status of shipped parcels to "handed over." The external service can perform this action by connecting to the carrier's track-and-trace API and deciding when to set the handoverJob status in fulfillmenttools to HANDED_OVER, according to the desired process.

Proof of concept: Connecting Uber Direct

Overview

As a proof of concept, this section demonstrates how to connect the carrier Uber Direct to fulfillmenttools. An external service, the Uber Connector Service, will be created to manage the integration.

This example uses Uber Direct due to its straightforward technical connection. It can be substituted with any other carrier. For this proof of concept, specific parameters are chosen, such as the trigger event, required data, and handover timing. This represents one of many possible implementation strategies; the approach can be adapted with different event triggers or more complex logic as needed.

In this example, the Parcel (with its label PDF) is automatically ordered when a PackJob is created for a given Process. The following diagram illustrates the components and events involved.

Drawing
A sequence diagram showing the interaction between fulfillmenttools, the Uber Connector Service, and the Uber Direct API.

Necessary steps

The integration connects to Uber Direct as the carrier, which offers a simple API for requesting pickups and deliveries.

The external service is hosted within a Google Cloud Project as a Cloud Run service, which scales to zero and responds to HTTP requests from a fulfillmenttools subscription.

Prepare the service infrastructure

This proof of concept is written in Kotlin. To create a running service, refer to the official Google Cloud documentationarrow-up-right for deploying a Kotlin service to Cloud Run.

circle-info

Platform Independence

The choice of hosting platform (e.g., GCP, Azure, AWS, on-premise) is irrelevant from an architectural standpoint. Any platform capable of hosting and operating the service may be used.

After completing this step, a live Cloud Run service is deployed and reachable via the internet without authentication on its endpoints.

triangle-exclamation

Configuration of fulfillmenttools

Following the guide on carrier management, create the custom carrier "Uber Direct" as shown below:

Next, connect this carrier to the facility where the service will be provided:

Implement the Uber Carrier Connector Service

The service is designed as a standalone application that does not require a database or other external components besides its container engine. This requires making practical decisions about which event to use and how much processing to perform during the event handling.

The created service is now extended with the following functionality. Only subsets of the implementation are described here.

An architecture diagram of the Uber Connector Service, showing its dependencies and interactions with fulfillmenttools and Uber APIs.

Implementing functionality for the Uber Direct API

First, an Uber account with access to the Uber Direct API is required. The following credentials must be obtained:

  • A Customer ID (UBER_CUSTOMER_ID)

  • A valid Client ID (UBER_CLIENT_ID)

  • A matching client secret (UBER_CLIENT_SECRET)

For simplicity, a new Bearer Token for the Uber APIarrow-up-right (UBER_TOKEN, valid for 30 days) is generated via curl and provided as a configuration variable to the service. This call obtains the token:

Next, the service must implement a call to Uber for the creation of a delivery arrow-up-right(pickup at location A, drop-off at location B). To accomplish this, a new Uber API object and the implementation for the delivery creation endpoint are created:

In addition, model classes for the request and response bodies, a repository, and a service are implemented for better code structure and readability.

Implement connections for the fulfillmenttools REST API

Functionality is also required to interact with fulfillmenttools to create parcels and set results. The following endpoints are the most important ones:

In addition to creating a parcel in fulfillmenttools containing the result of the Uber delivery, the service also needs the ability to set the HandoverJob created in the process to HANDED_OVER:

Provide endpoints for fulfillmenttools and Uber events

With the functionality in place, the web endpoints can be defined to receive events from fulfillmenttools and Uber. An event from fulfillmenttools will trigger the creation of a delivery, while track-and-trace events from Uber will trigger further processing later in the flow.

Deploy the service to Google Cloud Run

Issuing the command gcloud run deploy headlesscarrierservice in the console triggers the deployment of the service from its source code. After the build completes, the container is available at its assigned URL, for instance: https://headlesscarrierservice-558061097749.europe-west1.run.app.

This host URL is required for the remaining configuration steps in fulfillmenttools and Uber.

Create a fulfillmenttools subscription

To connect the service to a fulfillmenttools instance, create an HTTP subscription for the desired event. This is done by issuing the following API call, as detailed in the eventing documentation:

From this point on, every PACK_JOB_CREATED event within fulfillmenttools will be received and processed by the created service.

Configuration of Uber

Uber offers a webhook mechanismarrow-up-right similar to the subscription pattern in fulfillmenttools. A connection from Uber to the created service can be established by creating a webhook for event.delivery_status events in the Uber developer dashboard (example UI shown in German):

Screenshot of the Uber developer dashboard showing the webhook configuration for delivery status events.

Ensure that the correct service URL and endpoint are used. Once saved, Uber will send tracking events to the specified endpoint, where they will be handled by the service.

When an order is processed in the configured facility, this mechanism will trigger the creation of the delivery, upload a self-generated PDF to fulfillmenttools, and process the track-and-trace events.

In this article, we'll describe the end-to-end integration flow for custom carriers with external label creation and tracking updates, and how you can implement this. The flow applies to carriers that are configured with external label creation enabled.

circle-check

Prerequisites

Below is the visual representation of the custom carrier integration flow:

Once an order is created, the process begins. To set up a custom carrier to follow this flow, follow the steps below.

Operational processes and client-specific behavior aren't included in this article.

1

Connect the custom carrier to the facility

Use the endpoint below to connect the carrier to the facility.

After an order is processed and packed at the facility, a parcel is created in fulfillmenttools. If manualParcelHandlingActive is set to true for the facility carrier connection, the parcel is created with status PROCESSING, and the event PARCEL_CARRIER_REQUESTED is emitted. This event indicates that the integration layer is responsible for label creation.

If manualParcelHandlingActive is set to false, the parcel is created with status DONE without a label.

The emitted event contains, among others:

  • The parcel identifier

  • Carrier reference and carrier key

  • Delivery address information

  • Parcel dimensions and item information

PARCEL_CARRIER_REQUESTED indicates that the integration layer must create labels and update the parcel.

2

Receive parcel creation events for external label generation

Based on the received parcel information, the integration layer creates the shipment in the carrier system.

The integration layer obtains the required label and tracking data from the carrier system and provides it to fulfillmenttools.

For the ADD_LABELS_TO_PARCEL action, the following data is required for sendLabel:

  • File

    • content as a base64-encoded PDF

    • type with value PDF

  • type either SEND_LABEL or RETURN_LABEL

  • trackingNumber

  • trackingUrl as a fully qualified URL

Optionally, the same structure can be provided for returnLabel. If required, a customsDocument can be provided as a base64-encoded PDF.

3

Inject the shipping and return labels into parcels

After the integration layer has generated the required label and tracking data, the parcel is updated using the ADD_LABELS_TO_PARCEL action.

As part of the label injection, an optional tenantParcelId can be provided to reference the parcel in external systems.

If the label injection is successful and closeParcel is set to true, the parcel is closed and becomes eligible for handover processing.

We also recommend adding error handling at this stage using the same endpoint.

If label creation fails in the integration layer, the ADD_LABELS_TO_PARCEL action can be used to persist the error state. Error information is provided per label via sendLabel and returnLabel, using errorDescription and an optional errorCode. This allows fulfillmenttools to reflect the error state without attaching label content.

4

Update tracking data during the shipment lifecycle

After labels have been attached, tracking data can be updated during the shipment lifecycle.

Tracking updates are applied using the UPDATE_TRACKING_DATA action. The action supports updates for SEND_LABEL and RETURN_LABEL independently.

Each update can include status, an optional carrierStatus, and trackingNumber. Multiple updates can be applied over time to reflect shipment progress until the parcel reaches the end customer.

Tracking data updates can reference a parcel either by its parcelID or by a tenantParcelId. Both options use the same parcel actions endpoint. The distinction is made via the URL, while the request body and response remain unchanged.

Last updated