Fulfillment service apps can provide third-party warehousing, print on demand, or fulfillment services that prepare and ship orders on behalf of merchants.
This guide describes how to use the GraphQL Admin API to manage fulfillments in the following ways:
- Receive fulfillment requests and cancellations
- Accept and reject fulfillment requests and cancellations
- Exchange fulfillment requests notes with merchants
- Create and perform fulfillments for assigned work
- Optionally close fulfillment orders
## Requirements
- Your app can make authenticated requests to the [GraphQL Admin API](/docs/api/admin-graphql).
- Your app has the `write_fulfillments`, `read_assigned_fulfillment_orders` and `write_assigned_fulfillment_orders` [access scopes](/docs/api/usage/access-scopes). Learn how to [configure your access scopes using Shopify CLI](/docs/apps/build/cli-for-apps/app-configuration).
- Your store has existing orders that are unfulfilled. If you need to make changes to an unfulfilled item, then you can [edit an order](/docs/apps/build/orders-fulfillment/order-management-apps/edit-orders).
- You've met Shopify's [protected customer data requirements](/docs/apps/launch/protected-customer-data).
## Step 1: Create a fulfillment service
To begin managing fulfillments as a fulfillment service app, you need to create a fulfillment service in Shopify with a callback URL that will listen for fulfillment requests.
You can use the GraphQL Admin API's [`fulfillmentServiceCreate`](/docs/api/admin-graphql/latest/mutations/fulfillmentservicecreate) mutation to create a fulfillment service for each of your locations where you fulfill orders. Each fulfillment service corresponds to an inventory location in Shopify and allows you to manage fulfillment orders assigned to this location. Each physical fulfillment centre or warehouse should have its own fulfillment service.
To register a fulfillment service, your app requires the `write_fulfillments` access scope.
> Note:
> All fulfillment services are created with fulfillment orders opted in now.
## Step 2: Edit locations
After creating a fulfillment service for each of your fulfillment locations, you need to update the associated location address. This ensures that Shopify can have an accurate representation of your fulfillment network.
You can use the GraphQL Admin API's [`locationEdit`](/docs/api/admin-graphql/latest/mutations/locationEdit) mutation to update the address information of each location associated with your fulfillment services.
To update a location, your app requires either the `write_locations` or `write_fulfillments` access scope.
- `write_locations` - This is required to modify merchant-managed locations
- `write_fulfillments` - The app that created the fulfillment service can edit locations associated with it
Repeat [step 1](#step-1-create-a-fulfillment-service) and [step 2](#step-2-edit-locations) for each fulfillment location you operate from until all fulfillment locations have their own fulfillment services. Ensure that you provide a unique `name` for each fulfillment service to distinguish between your different locations. A `callbackUrl` can be shared by multiple fulfillment services.
## Step 3: Receive fulfillment requests and cancellations
Apps must configure an endpoint on a callback URL where Shopify will send the notifications for fulfillment requests. This is a requirement to receive requests from Shopify. The endpoint should be structured in the following format: `/fulfillment_order_notification`.
The payloads that the endpoint receives include the `kind` property or field, which can have one of the following values:
| Value | Description |
|---|---|
| `FULFILLMENT_REQUEST` | When a merchant requests a fulfillment in their Shopify admin |
| `CANCELLATION_REQUEST` | When a merchant submits a cancellation request in their Shopify admin |
The following examples show payloads for each type of request:
## Step 4: Act on fulfillment requests
Each fulfillment order has a [status and a request status](/docs/apps/build/orders-fulfillment/fulfillment-service-apps#statuses), both of which indicate the state of the fulfillment order.
A fulfillment order notification of `FULFILLMENT_REQUEST` indicates that there’s a new fulfillment order assigned to the service, and it’s ready to be acted on because the merchant has requested a fulfillment.
Fulfillment services can [retrieve assigned fulfillment order requests](#retrieve-assigned-fulfillment-order-requests), and specify the fields that are required to determine whether it should [accept](#accept-a-fulfillment-request) or [reject](#reject-a-fulfillment-request) a fulfillment request. Requests can be [validated with the same HMAC validation](/docs/apps/build/webhooks/subscribe/https) method that's used when Shopify sends webhooks to apps.
### Retrieve assigned fulfillment order requests
The following example shows how to use the [`assignedFulfillmentOrders`](/docs/api/admin-graphql/latest/objects/queryroot#connection-assignedfulfillmentorders) connection to request the first 10 assigned fulfillment orders with the `FULFILLMENT_REQUESTED` status.
The request includes:
- Fulfillment order destination, line items, and line item SKUs.
- Merchant request notes from the `merchantRequests` field.
### Accept a fulfillment request
After retrieving the assigned fulfillment orders, the fulfillment service can review each fulfillment order, and determine whether it can be fulfilled.
To accept a fulfillment order, the fulfillment service must send an accepted request using the GraphQL Admin API's [`fulfillmentOrderAcceptFulfillmentRequest`](/docs/api/admin-graphql/latest/mutations/fulfillmentOrderAcceptFulfillmentRequest) mutation:
After the fulfillment service sends an accepted request, the fulfillment order card in the Shopify admin indicates to the merchant that the request was accepted:
### Reject a fulfillment request
If the fulfillment service determines that it can’t fulfill the order, then it can reject the fulfillment request using the [fulfillmentOrderRejectFulfillmentRequest](/docs/api/admin-graphql/latest/mutations/fulfillmentOrderRejectFulfillmentRequest) mutation:
After the fulfillment service sends a rejected request, the fulfillment order card in the Shopify admin indicates to the merchant that the request was rejected:
## Step 5: Create fulfillments
After [accepting a fulfillment request](#accept-a-fulfillment-request), the fulfillment service can begin creating fulfillments using the GraphQL Admin API's [`fulfillmentCreateV2`](/docs/api/admin-graphql/latest/mutations/fulfillmentCreateV2) mutation. The fulfillment service can create multiple fulfillments for a given fulfillment order if required, representing multiple packages to be shipped.
When an app accepts a fulfillment request, the fulfillment order status transitions to in progress. After all line items on the fulfillment order are entirely fulfilled, the fulfillment order status transitions to a closed state.
> Note:
> Fulfillments can’t be created for a fulfillment order before the fulfillment service accepts the fulfillment request.
The following examples show how to create a fulfillment for a single line item, with a quantity of three. The response provides a success status, which indicates that the fulfillment has been successfully created.
## Step 6: Act on cancellation requests
A fulfillment order notification of `CANCELLATION_REQUEST` indicates that a merchant has requested for a fulfillment order to be cancelled. Similar to [retrieving assigned fulfillment order requests](#retrieve-assigned-fulfillment-order-requests), the fulfillment service can retrieve cancellation requests.
After retrieving cancellation requests, the fulfillment service can either [accept](#accept-a-cancellation-request) or [reject](#reject-a-cancellation-request) the cancellation request. If the fulfillment service accepts the cancellation request, then it can [cancel the fulfillment](#step-7-cancel-a-fulfillment).
> Tip:
> It might take a few minutes for cancellation requests to show up as a part of your assigned fulfillment orders. We recommend polling your assigned fulfillment orders until you receive the cancellation request.
### Merchants cancelling fulfillment orders
Merchants can cancel a fulfillment order before the fulfillment service responds to the cancellation request. This option is provided immediately after requesting cancellation. A warning message is also displayed to the merchant, indicating that forcibly cancelling a fulfillment order doesn’t guarantee that the fulfillment service won't ship the items.
After the fulfillment service responds by either rejecting or accepting the cancellation request, the option is no longer provided to the merchant. The fulfillment service can’t create fulfillments against a fulfillment order that was cancelled by a merchant.
### Retrieve cancellation requests
The following example shows how to use the [`assignedFulfillmentOrders`](/docs/api/admin-graphql/latest/objects/Shop#connection-shop-assignedfulfillmentorders) connection to query cancellation requests. Pass in the `assignmentStatus` argument and set it to `CANCELLATION_REQUEST` to retrieve assigned fulfillment orders with the `CANCELLATION_REQUEST` status.
> Note:
> A fulfillment service can’t create fulfillments for the fulfillment orders that are returned in assigned fulfillment orders until it accepts or rejects the cancellation requests.
### Accept a cancellation request
After retrieving the assigned fulfillment order cancellation requests, the fulfillment service can review each fulfillment order, and determine whether it can be cancelled.
If the fulfillment order can be cancelled, then the fulfillment service can use the GraphQL Admin API's [`fulfillmentOrderAcceptCancellationRequest`](/docs/api/admin-graphql/latest/mutations/fulfillmentOrderAcceptCancellationRequest) mutation to accept the cancellation of the fulfillment order and send an optional message to the merchant.
The response returns a fulfillment order that has a request status of cancellation accepted:
After the fulfillment service accepts a cancellation request, the fulfillment order card in the Shopify admin indicates to the merchant that the cancellation request was accepted:
### Reject a cancellation request
If the fulfillment service determines that it can’t accept the cancellation of the fulfillment order, then it can reject the cancellation request using the the GraphQL Admin API's [`fulfillmentOrderRejectCancellationRequest`](/docs/api/admin-graphql/latest/mutations/fulfillmentOrderRejectCancellationRequest) mutation.
The response returns a fulfillment order that has a request status of cancellation rejected. The fulfillment service can continue to [create fulfillments](#step-5-create-fulfillments) after rejecting a cancellation.
After the fulfillment service rejects a cancellation request, the fulfillment order card in the Shopify admin indicates to the merchant that the cancellation request was rejected:
## Step 7: Cancel a fulfillment
You can cancel a fulfillment using the GraphQL Admin API's [`fulfillmentCancel`](/docs/api/admin-graphql/latest/mutations/fulfillmentCancel) mutation. However, any fulfillment orders that the fulfillment was created against will be affected in the following ways:
- If the underlying fulfillment order was entirely fulfilled, then it will be automatically closed.
- After cancelling a fulfillment, a new fulfillment order is created consisting of the line items from the cancelled fulfillment. This new fulfillment order is assigned to the same location as the original fulfillment order if the items are still stocked there.
- If all items from the cancelled fulfillment can't be sourced from the same location, then the new fulfillment order is assigned to a location where the items are stocked, considering the shop’s fulfillment priority settings. This might result in multiple newly opened fulfillment orders for different locations.
- If the fulfillment order was partially fulfilled, then the fulfillment order line item and remaining quantity properties / fields are adjusted back based on the cancelled fulfillment’s line items.
After a fulfillment service cancels a fulfillment, the fulfillment order card in the Shopify admin notifies the merchant that the fulfillment request has been cancelled:
## Step 8 (Optional): Close a fulfillment order
In some cases, a fulfillment service might realize that it can’t fulfill the requested items only after it’s already accepted a fulfillment order request.
The fulfillment service can use the GraphQL Admin API's [`fulfillmentOrderClose`](/docs/api/admin-graphql/latest/mutations/fulfillmentOrderClose) mutation to close the fulfillment order and indicate to the merchant that they won't be fulfilling the order.
Closing a fulfillment results in a fulfillment order where the status is incomplete and the request status is closed.
> Note:
> If a merchant makes a new fulfillment request on an order that had a fulfillment order marked as incomplete, then the original incomplete fulfillment order will have a closed status and a new fulfillment order will be created.
## Step 9 (Optional): Enable tracking support
Your app can optionally act on Shopify requests for tracking numbers by exposing a `/fetch_tracking_numbers` endpoint that's prefixed with the `callback_url` that you set in [step 3](#step-3-receive-fulfillment-requests-and-cancellations).
If `tracking_support` is set to `true`, then once each hour, Shopify makes a request to the `/fetch_tracking_numbers` endpoint to determine if there are any completed fulfillments awaiting tracking numbers from the remote fulfillment service.
## Step 10 (Optional): Share inventory levels with Shopify
Your app can optionally act on Shopify requests for inventory levels by exposing a `/fetch_stock` endpoint that's prefixed with the `callback_url` that you set in [step 3](#step-3-receive-fulfillment-requests-and-cancellations).
If `fulfillment_service.inventory_management` is set to `true`, then Shopify makes a request to the `/fetch_stock` endpoint for the inventory of an individual SKU when the product is set up initially, when its SKU is changed in the Shopify admin, or when its inventory management is set to use the fulfillment service. Please note that SKU strings are case-sensitive at Shopify, and the SKUs returned from the `/fetch_stock` endpoint will be matched to the Shopify product SKUs respecting the case.
A request for all inventory data happens once every hour to keep Shopify up to date with the remote fulfillment service.
The format for the payload is a JSON object where the keys are the SKUs and the values are the `on_hand` inventory state levels.
## Webhooks
In API version 2023-01 and higher, your app can subscribe to webhooks for event notifications related to fulfillment orders. The following examples show the JSON responses from each of the available webhooks.
To learn how to set up and consume webhooks, refer to [Webhooks configuration overview](/docs/apps/build/webhooks/subscribe).
## Next steps
- Learn about the recommended workflow for using Shopify APIs to track orders placed through [third-party marketplaces](/docs/apps/build/orders-fulfillment/order-management-apps/track-orders-other-platforms).
- Learn how to manage inventory using the GraphQL Admin API by referring to the [`inventoryItem`](/docs/api/admin-graphql/latest/queries/inventoryItem) query and [`inventoryActivate`](/docs/api/admin-graphql/latest/mutations/inventoryActivate) mutation in the reference documentation.