Skip to main content

Order fulfillments

The order fulfillments API allows you to create, view, update, and delete fulfillments for an order, along with their metadata (tracking numbers, shipment provider, and any custom data you want to attach).

info

These endpoints are only available when the Order Fulfillments feature is enabled (woocommerce_feature_fulfillments_enabled option). If the feature is disabled, these routes are not registered and requests to them will return a 404 rest_no_route error.

Order fulfillment properties​

AttributeTypeDescription
idintegerUnique identifier for the fulfillment. READ-ONLY
entity_typestringThe type of entity this fulfillment belongs to. Currently always WC_Order. READ-ONLY
entity_idstringThe ID of the entity this fulfillment belongs to, i.e. the order ID. READ-ONLY
statusstringStatus of the fulfillment, e.g. unfulfilled or fulfilled. Custom statuses can also be used. Default is unfulfilled.
is_fulfilledbooleanWhether the fulfillment is fulfilled. Default is false.
date_updateddate-timeThe date the fulfillment was last updated, as GMT, in ISO 8601 format (e.g. 2024-01-15T11:45:00Z). READ-ONLY
date_deleteddate-timeThe date the fulfillment was deleted, as GMT, or null if it hasn't been deleted. READ-ONLY
meta_dataarrayMeta data. See Order fulfillment - Meta data properties

The following parameters are also accepted when creating or updating a fulfillment, but are not part of the stored resource:

AttributeTypeDescription
notify_customerbooleanWhether to notify the customer about this change. Default is false. WRITE-ONLY
customer_notestringAn optional note from the merchant, included in the update notification email. Only used when updating a fulfillment. WRITE-ONLY
note

When you create or update a fulfillment, only status, is_fulfilled, and meta_data from the JSON body are applied to the fulfillment. The order ID comes from the URL path. On update, the fulfillment ID also comes from the URL path. On create, the server assigns it. A top-level id, entity_type, or entity_id in the body is silently ignored, so a request body cannot change which order or fulfillment is written to. These three fields are still returned in responses. notify_customer and customer_note (update only) are request parameters that control notifications. They are not stored on the fulfillment.

Order fulfillment - Meta data properties​

AttributeTypeDescription
idintegerMeta ID. 0 for entries that haven't been saved yet. READ-ONLY
keystringMeta key.
valuemixedMeta value. Can be a string, number, array, or object.

The _items meta key is required and must be an array of objects describing which order line items are being fulfilled:

[
{
"item_id": 456,
"qty": 2
}
]
FieldTypeDescription
item_idintegerThe order line item ID.
qtynumberPositive quantity of the item being fulfilled.

Other common (optional) meta keys: _tracking_number, _shipment_provider, _tracking_url, and _date_fulfilled (stored as GMT and returned in ISO 8601 format, e.g. 2024-01-16T11:45:00Z). Meta keys prefixed with an underscore are private and only shown to merchants and in customer emails; unprefixed keys are also shown to merchants and customers alongside the fulfillment.

List fulfillments for an order​

GET /wp-json/wc/v3/orders/<id>/fulfillments
curl https://example.com/wp-json/wc/v3/orders/723/fulfillments \
-u consumer_key:consumer_secret

Create a fulfillment​

On create, meta_data is itself a required parameter (it must contain at least the _items entry). Omitting the meta_data wrapper returns a 400 with the code rest_missing_callback_param.

POST /wp-json/wc/v3/orders/<id>/fulfillments
curl -X POST "https://example.com/wp-json/wc/v3/orders/723/fulfillments?notify_customer=true" \
-u consumer_key:consumer_secret \
-H "Content-Type: application/json" \
-d '{
"status": "fulfilled",
"is_fulfilled": true,
"meta_data": [
{
"key": "_items",
"value": [
{ "item_id": 456, "qty": 2 }
]
},
{
"key": "_tracking_number",
"value": "1Z999AA1234567890"
},
{
"key": "_shipment_provider",
"value": "ups"
}
]
}'

Setting notify_customer=true when the fulfillment is created as fulfilled triggers the woocommerce_fulfillment_created_notification action, which sends the default fulfillment email.

Retrieve a fulfillment​

GET /wp-json/wc/v3/orders/<id>/fulfillments/<fulfillment_id>
curl https://example.com/wp-json/wc/v3/orders/723/fulfillments/2 \
-u consumer_key:consumer_secret

Update a fulfillment​

Any meta_data entries not included in the request are removed from the fulfillment, so send the full set of metadata you want the fulfillment to have. As a safeguard against accidental data loss, if the request's meta_data is non-empty but every entry is malformed (no valid key), the existing metadata is left untouched rather than cleared.

PUT /wp-json/wc/v3/orders/<id>/fulfillments/<fulfillment_id>
curl -X PUT "https://example.com/wp-json/wc/v3/orders/723/fulfillments/2?notify_customer=true" \
-u consumer_key:consumer_secret \
-H "Content-Type: application/json" \
-d '{
"status": "fulfilled",
"is_fulfilled": true,
"customer_note": "Your package is on its way!",
"meta_data": [
{
"key": "_items",
"value": [
{ "item_id": 456, "qty": 2 }
]
},
{
"key": "_tracking_number",
"value": "1Z999AA1234567890"
},
{
"key": "_shipment_provider",
"value": "ups"
},
{
"key": "_delivery_date",
"value": "2024-01-16"
}
]
}'

notify_customer=true triggers woocommerce_fulfillment_created_notification if the fulfillment is being marked as fulfilled for the first time, or woocommerce_fulfillment_updated_notification for subsequent updates to an already-fulfilled fulfillment. customer_note is only used for the update notification email.

Delete a fulfillment​

DELETE /wp-json/wc/v3/orders/<id>/fulfillments/<fulfillment_id>
curl -X DELETE "https://example.com/wp-json/wc/v3/orders/723/fulfillments/2?notify_customer=true" \
-u consumer_key:consumer_secret

Deleting a fulfilled fulfillment with notify_customer=true triggers the woocommerce_fulfillment_deleted_notification action.

Get fulfillment metadata​

GET /wp-json/wc/v3/orders/<id>/fulfillments/<fulfillment_id>/metadata
curl https://example.com/wp-json/wc/v3/orders/723/fulfillments/2/metadata \
-u consumer_key:consumer_secret

Update fulfillment metadata​

Like updating a fulfillment, this replaces the full metadata set: any existing meta keys not present in the request body are removed.

The metadata must be wrapped in an object under the meta_data key. The endpoint requires the meta_data parameter, so sending a bare array returns a 400 rest_missing_callback_param error. Because the request replaces the full set, keep _items in the payload. The metadata update and delete endpoints cannot remove _items; saving without it fails with "The fulfillment should contain at least one item."

PUT /wp-json/wc/v3/orders/<id>/fulfillments/<fulfillment_id>/metadata
curl -X PUT https://example.com/wp-json/wc/v3/orders/723/fulfillments/2/metadata \
-u consumer_key:consumer_secret \
-H "Content-Type: application/json" \
-d '{
"meta_data": [
{
"key": "_items",
"value": [
{ "item_id": 456, "qty": 2 }
]
},
{
"key": "_tracking_number",
"value": "1Z999AA9876543210"
},
{
"key": "_shipment_provider",
"value": "fedex"
}
]
}'

Delete fulfillment metadata​

Deletes a single metadata entry by key and returns the fulfillment's remaining metadata.

DELETE /wp-json/wc/v3/orders/<id>/fulfillments/<fulfillment_id>/metadata
curl -X DELETE https://example.com/wp-json/wc/v3/orders/723/fulfillments/2/metadata \
-u consumer_key:consumer_secret \
-H "Content-Type: application/json" \
-d '{
"meta_key": "_shipment_provider"
}'

Look up a tracking number​

Given a tracking number, tries to determine the shipment provider and tracking URL. Useful when you have a tracking number but don't know (or want to guess) which carrier it belongs to.

GET /wp-json/wc/v3/orders/<id>/fulfillments/lookup
curl "https://example.com/wp-json/wc/v3/orders/723/fulfillments/lookup?tracking_number=1234567890123456" \
-u consumer_key:consumer_secret

Available parameters​

ParameterTypeDescription
tracking_numberstringRequired. The tracking number to look up.

The two error cases are different and return different codes:

  • Missing tracking_number: WordPress rejects the request before the endpoint runs, because the parameter is registered as required. The response is a 400 with the code rest_missing_callback_param.
  • Empty tracking_number (for example ?tracking_number=): the request reaches the endpoint and returns a 400 with the code woocommerce_rest_tracking_number_missing.

Error responses​

Errors follow the standard WooCommerce REST API error shape:

{
"code": "error_code",
"message": "Error message description",
"data": {
"status": 400
}
}

Common error codes for the fulfillments endpoints:

  • woocommerce_rest_order_invalid_id - The order ID does not exist.
  • woocommerce_rest_order_id_missing - The order ID is required but was not supplied.
  • rest_missing_callback_param - A required parameter was omitted, for example a missing tracking_number.
  • woocommerce_rest_tracking_number_missing - The tracking_number parameter was supplied but is empty.
  • Standard authentication/authorization errors. Creating, updating, and deleting fulfillments requires the manage_woocommerce capability. A customer who owns the order (but lacks manage_woocommerce) has read-only access: they can list and retrieve the order's fulfillments and metadata, but write requests return an authorization error.