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 API. 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.
Subscribe to a
fulfillmenttoolsevent. 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 asPACK_JOB_CREATED.fulfillmenttoolssends this event to the external service that handles the label request from the Carrier API.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
fulfillmenttoolsvia its API.Request a label from the Carrier API. Similar to the native
fulfillmenttoolsmechanism, 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.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 tofulfillmenttoolsfor seamless process integration. For example, the label is uploaded to adocumentset, and the track-and-trace data is stored as a result on theparcelentity.Set handover states based on track-and-trace data.
fulfillmenttoolscan 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 thehandoverJobstatus infulfillmenttoolstoHANDED_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.
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 documentation for deploying a Kotlin service to Cloud Run.
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.
Production Security
This proof of concept omits security measures for simplicity. A production-ready service must implement robust authentication and authorization mechanisms to protect its endpoints.
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.

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 API (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 (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 mechanism 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):

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.
Prerequisites
A custom carrier has been created with a key starting with
CUSTOM_A webhook endpoint is configured to receive HTTP POST callbacks for parcel events
The integration layer is capable of generating shipping labels and tracking information via the carrier system
For more information, see the Configuration of fulfillmenttools section in the Custom carrier article.
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.
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.
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
contentas a base64-encoded PDFtypewith value PDF
typeeitherSEND_LABELorRETURN_LABELtrackingNumbertrackingUrlas 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.
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.
This functionality is currently a work in progress. The parcel response payload might be extended in the future. Integrations that persist data from the response should be prepared to adjust their connector logic accordingly.
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.
This functionality is currently a work in progress. The parcel response payload may be extended in the future. Integrations that persist data from the response should be prepared to adjust their connector logic accordingly.
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.
This functionality is currently a work in progress. The parcel response payload may be extended in the future. Integrations that persist data from the response should be prepared to adjust their connector logic accordingly.
Last updated