shopify mark order as fulfilled api

Automating fulfillment is a critical step for scaling any Shopify store. The process of marking an order as fulfilled programmatically centers on creating a Fulfillment object via Shopify's APIs. This action updates the order status, triggers customer notifications, and posts tracking information.

This technical guide covers the steps, code samples, and endpoints developers need to manage the entire fulfillment lifecycle using both the REST Admin API and the GraphQL Admin API. Topics include creating fulfillments for specific line items, managing tracking data, configuring fulfillment notifications, handling cancellations, and understanding the required API access scopes.

Looking to offload the complexity of building and maintaining these integrations? See how 4fillment's integration works with Shopify stores out of the box.

The Fulfillment Order Lifecycle: From Open to Closed

Before writing any code, it's essential to understand Shopify's fulfillment data model. When an order is created, Shopify generates one or more FulfillmentOrder objects. A FulfillmentOrder represents a group of line items that must be fulfilled from a single location. Your application's job is to query these objects and create a Fulfillment for them once they ship.

A FulfillmentOrder moves through several statuses that dictate which actions are possible:

  • OPEN: The initial state. The fulfillment order is ready to be actioned. You can query for fulfillment orders with this status to begin your fulfillment process.
  • IN_PROGRESS: The fulfillment has been accepted by a fulfillment service or app, but a fulfillment has not yet been created.
  • ON_HOLD: The fulfillment is intentionally paused. You cannot create a fulfillment for an order in this state.
  • CLOSED: All line items in the fulfillment order have been fulfilled. No further action is needed.
  • CANCELLED: The fulfillment order was cancelled before any items were shipped.
  • INCOMPLETE: A fulfillment was attempted but failed, often due to an issue that requires manual intervention.

A well designed integration will listen for new FulfillmentOrder objects in an OPEN state, move them to IN_PROGRESS, and ultimately create a Fulfillment to transition the status to CLOSED. For a deeper look at how the fulfillment order model fits together, read this fulfillment orders Shopify API guide.

How to Use the Shopify Mark Order as Fulfilled API

To mark items as fulfilled, you must create a Fulfillment. This object contains tracking information and links back to the FulfillmentOrder it satisfies. You can use either the REST or GraphQL API.

Creating a Fulfillment with the REST API

Use the Fulfillment resource to create a fulfillment. You'll make a POST request to the /fulfillments.json endpoint, specifying which line items from which fulfillment orders are included in the shipment.

Endpoint:

POST /admin/api/2024-04/fulfillments.json

Required Scopes: write_fulfillments, read_fulfillments

In this example, we fulfill items from fulfillment order gid://shopify/FulfillmentOrder/1046000781 and notify the customer.

Request Example (cURL):

curl -X POST \
  "https://{shop}.myshopify.com/admin/api/2024-04/fulfillments.json" \
  -H "X-Shopify-Access-Token: {access_token}" \
  -H "Content-Type: application/json" \
  -d '{
    "fulfillment": {
      "line_items_by_fulfillment_order": [
        {
          "fulfillment_order_id": 1046000781,
          "fulfillment_order_line_items": [
            { "id": 1058737551, "quantity": 1 }
          ]
        }
      ],
      "tracking_info": {
        "number": "1Z23456789",
        "url": "https://www.mycarrier.com/tracking/1Z23456789",
        "company": "MyCarrier"
      },
      "notify_customer": true
    }
  }'

Note on Multiple Tracking Numbers: A single Fulfillment object supports only one tracking number. If an order ships in multiple packages, you must create a separate Fulfillment for each package.

Creating a Fulfillment with the GraphQL API

The GraphQL Admin API offers a more flexible and efficient way to create fulfillments using the fulfillmentCreateV2 mutation. This allows you to fulfill items from one or more fulfillment orders in a single API call.

Mutation: fulfillmentCreateV2

Required Scopes: write_fulfillments, read_fulfillments

This mutation fulfills one line item and provides tracking details.

Request Example (GraphQL Mutation):

mutation fulfillmentCreateV2($fulfillment: FulfillmentV2Input!) {
  fulfillmentCreateV2(fulfillment: $fulfillment) {
    fulfillment {
      id
      status
      trackingInfo {
        number
        url
      }
    }
    userErrors {
      field
      message
    }
  }
}

Variables:

{
  "fulfillment": {
    "lineItemsByFulfillmentOrder": [
      {
        "fulfillmentOrderId": "gid://shopify/FulfillmentOrder/1046000781",
        "fulfillmentOrderLineItems": [
          {
            "id": "gid://shopify/FulfillmentOrderLineItem/1058737551",
            "quantity": 1
          }
        ]
      }
    ],
    "trackingInfo": {
      "company": "MyCarrier",
      "number": "1Z23456789",
      "url": "https://www.mycarrier.com/tracking/1Z23456789"
    },
    "notifyCustomer": true
  }
}

Many modern Shopify integrations prefer GraphQL for its ability to reduce the number of API calls and for its strongly typed schema, which helps prevent errors.

Notifications About Fulfillment Orders

Getting real time notifications when fulfillment orders are created or updated is just as important as knowing how to fulfill them. Without proper notification handling, your integration is stuck polling the API, which wastes rate limit budget and introduces delays.

Subscribing to Fulfillment Order Webhooks

Shopify provides several webhook topics related to fulfillment orders. These fire automatically when the relevant event occurs in the store, so your application can react immediately rather than repeatedly querying for changes.

The most important webhook topics for fulfillment integrations:

  • fulfillment_orders/order_routing_complete: Fires after Shopify finishes routing an order's line items to their assigned fulfillment locations. This is typically the signal that a new fulfillment order is ready for your app to pick up.
  • fulfillments/create: Fires when a fulfillment is created on an order. Useful if your system needs to track fulfillments created outside your integration (for example, manually by a merchant).
  • fulfillments/update: Fires when an existing fulfillment is modified, such as when tracking information changes.

According to Shopify's webhook documentation, apps should subscribe to mandatory compliance webhooks as well, but for fulfillment automation, the fulfillment_orders/order_routing_complete topic is your primary trigger.

Registering a Webhook via the GraphQL API

You can register webhooks programmatically using the webhookSubscriptionCreate mutation:

mutation {
  webhookSubscriptionCreate(
    topic: FULFILLMENT_ORDERS_ORDER_ROUTING_COMPLETE
    webhookSubscription: {
      callbackUrl: "https://yourapp.com/webhooks/fulfillment_orders"
      format: JSON
    }
  ) {
    webhookSubscription {
      id
    }
    userErrors {
      field
      message
    }
  }
}

Once registered, Shopify sends a POST request to your callback URL every time an order's routing completes. The payload includes the fulfillment order IDs, so your app can immediately query for the full details and begin the fulfillment process.

How Notification Flow Works in Practice

Here is a typical notification driven workflow:

  1. A customer places an order on the Shopify store.
  2. Shopify creates one or more FulfillmentOrder objects and routes them to the appropriate fulfillment locations.
  3. Once routing finishes, Shopify fires the fulfillment_orders/order_routing_complete webhook to your registered endpoint.
  4. Your app receives the webhook, queries for the fulfillment order details (status, line items, quantities), and queues the order for picking and packing.
  5. After shipping, your app creates a Fulfillment via the API with the notifyCustomer: true flag, which triggers Shopify's built in shipping confirmation email to the customer.

Practitioners on Reddit report that a common mistake is relying on the orders/create webhook instead of the fulfillment order specific topics. The problem: orders/create fires before routing completes, so the fulfillment order IDs may not be available yet. This leads to race conditions where the app tries to query fulfillment orders that don't exist, resulting in empty responses and retry logic headaches.

The notifyCustomer Flag

The notifyCustomer boolean (or notify_customer in REST) controls whether Shopify sends its standard shipping confirmation email when a fulfillment is created or updated. Setting it to true triggers the "Your order is on the way" email template configured in the Shopify admin under Settings > Notifications.

A few things to keep in mind:

  • If your app sends its own branded shipping notifications, set notifyCustomer to false to avoid duplicate emails.
  • Updating tracking information with notify_customer: true sends a second notification with the new tracking details, which is useful for correcting an incorrect tracking number.
  • There is no API control over the email template content itself. Merchants customize that in their Shopify admin.

For brands working with a tech enabled fulfillment provider, these webhook and notification patterns are typically handled by the 3PL's integration layer. The fulfillment partner's app subscribes to the relevant webhooks, processes orders automatically, and pushes fulfillments back with tracking, all without the merchant needing to write custom code.

Webhook Security: Validating Payloads

Every webhook Shopify sends includes an HMAC SHA256 signature header. Your endpoint should validate this signature against your app's shared secret before processing the payload. Skipping this step opens your system to spoofed requests. Shopify's developer docs include language specific examples for this verification.

Managing and Accessing Fulfillment Data

Your application will need to fetch fulfillment orders to know what work needs to be done and manage access permissions carefully.

Retrieving Fulfillment Orders for an Order

Before you can create a fulfillment, you must get the ID of the open FulfillmentOrder. You can retrieve all fulfillment orders associated with a specific order.

REST API Endpoint:

GET /admin/api/2024-04/orders/{order_id}/fulfillment_orders.json

GraphQL API Query:

query getFulfillmentOrders($orderId: ID!) {
  order(id: $orderId) {
    id
    fulfillmentOrders(first: 10) {
      edges {
        node {
          id
          status
          requestStatus
          lineItems(first: 10) {
            edges {
              node {
                id
                totalQuantity
                remainingQuantity
              }
            }
          }
        }
      }
    }
  }
}

This query provides a complete picture of an order's fulfillment status, including which items still need to be shipped (remainingQuantity).

Understanding Fulfillment Order Access Scopes

Shopify's API access scopes ensure that apps can only access the data they are authorized to see. This is particularly important for fulfillment apps.

Key Scopes:

  • write_merchant_managed_fulfillment_orders: For private apps managing a merchant's own warehouse fulfillments.
  • write_assigned_fulfillment_orders: For public or custom apps acting as a third party fulfillment service. The app will only receive fulfillment orders assigned to its associated fulfillment service and location.

When you partner with a 3PL, their app requests the assigned_fulfillment_orders scope. This grants them permission to manage only the orders routed to their warehouses, protecting the data of orders you might fulfill in house or through other partners.

Handling Updates and Exceptions

Fulfillment workflows require handling exceptions, such as updating tracking information or canceling a shipment.

Updating Fulfillment Tracking Information

If a tracking number was entered incorrectly or becomes available after the fulfillment was created, you can update it using the REST API or the fulfillmentTrackingInfoUpdateV2 GraphQL mutation.

REST API Endpoint:

POST /admin/api/2024-04/fulfillments/{fulfillment_id}/update_tracking.json

Request Example (cURL):

curl -X POST \
  "https://{shop}.myshopify.com/admin/api/2024-04/fulfillments/1029926391/update_tracking.json" \
  -H "X-Shopify-Access-Token: {access_token}" \
  -H "Content-Type: application/json" \
  -d '{
    "fulfillment": {
      "tracking_info": {
        "number": "1Z999AA10123456784",
        "company": "MyCarrier"
      },
      "notify_customer": true
    }
  }'

Accurate tracking data is crucial for customer satisfaction and can significantly reduce "Where is my order?" (WISMO) support inquiries. Getting the order fulfillment process right from pick to ship confirmation makes a measurable difference in support ticket volume.

Canceling a Fulfillment

If a shipment needs to be stopped before it leaves the warehouse, you can cancel the fulfillment. For merchant managed fulfillments, this is a direct action. For fulfillments handled by a third party, it becomes a cancellation request that the fulfillment service must accept or reject.

REST API Endpoint:

POST /admin/api/2024-04/fulfillments/{fulfillment_id}/cancel.json

GraphQL Mutation: fulfillmentCancel

A modern fulfillment partner's system receives this request via webhook, attempts to stop the shipment, and responds via the API, ensuring inventory and order statuses remain synchronized between systems. Operations backed by robotic automation can catch cancellation requests faster because the system tracks package status in real time before handoff to the carrier.

Choosing Between Building and Outsourcing Fulfillment Integrations

Building a Shopify fulfillment integration from scratch gives you full control, but it also means maintaining webhook endpoints, handling edge cases around partial fulfillments, managing retry logic for failed API calls, and keeping up with Shopify's API versioning cycle (they deprecate versions roughly every year).

For brands scaling past a few hundred orders per day, the maintenance burden grows. Missed webhooks, stale API versions, and unhandled error states can result in orders stuck in limbo. Practitioners in Shopify developer forums frequently mention that the gap between "it works in testing" and "it works reliably at scale" is larger than expected.

This is where outsourcing to a 3PL with direct Shopify integration becomes practical. The 3PL's engineering team maintains the integration, handles API updates, and processes fulfillment events through battle tested infrastructure.

Talk to a fulfillment specialist about offloading your Shopify fulfillment automation.

Frequently Asked Questions

What is the difference between a Fulfillment and a Fulfillment Order in Shopify?

A FulfillmentOrder is a request from Shopify to a location indicating which items from an order need to be shipped. A Fulfillment is the record you create via the API to confirm that the items have been shipped, including tracking details. Think of the Fulfillment Order as the task and the Fulfillment as the completion record.

Can I partially fulfill an order using the Shopify API?

Yes. When creating a fulfillment, you can specify which line items and what quantity of each you are shipping. The remaining items will stay on the open FulfillmentOrder with a non zero remainingQuantity, ready for a subsequent fulfillment.

What permissions do I need to use the Shopify mark order as fulfilled API?

Your app needs the appropriate write access scopes. This is typically write_merchant_managed_fulfillment_orders and write_fulfillments for apps managing your own warehouse, or write_assigned_fulfillment_orders for apps acting as a third party fulfillment service.

How do I handle multiple tracking numbers for one order with the API?

You must create a separate Fulfillment resource for each tracking number. Shopify's data model maps one fulfillment to one shipment or package. If an order ships in two boxes, you must make two API calls to create two fulfillments, each with its own tracking information.

Why might a fulfillment be stuck in an "In Progress" state?

This typically happens when a fulfillment service has accepted a FulfillmentOrder but has not yet followed up by creating a Fulfillment to close it out. This can indicate a bug in the integration, a failed webhook, or a delay in the fulfillment partner's internal system. Monitoring fulfillment KPIs helps catch these stuck states before they pile up.

Is it better to use the REST or GraphQL API for fulfillments?

For simple, single action scripts, the REST API is often quicker to implement. For complex applications that require fetching related data (order details and fulfillment orders at once) or performing multiple actions, the GraphQL API is more efficient, reducing network latency and providing stronger type safety.

How do I get notified when a new fulfillment order is created?

Subscribe to the fulfillment_orders/order_routing_complete webhook topic. This fires after Shopify finishes routing line items to their assigned fulfillment locations, giving your app the signal to query for new fulfillment orders and begin processing. Avoid relying on orders/create for this purpose, as fulfillment order IDs may not be available at that point.

Can I control whether customers receive shipping notification emails?

Yes. Both the REST and GraphQL APIs include a notify_customer (or notifyCustomer) boolean when creating or updating fulfillments. Set it to true to trigger Shopify's built in shipping confirmation email, or false if your system handles notifications separately.