Skip to main content

Receive Fulfillment Order Receipts

Playbook can process fulfillment order receipts from one ore many warehouses via a webhook endpoint.

When the Webhook - Process Shipments Playbook Step is installed on a business it provides a synchronous endpoint that can be used to post order fulfillment request acknowledgements (fulfillment order receipts) back to ChannelApe. It handles much more than our order update endpoint. It will match up to pending fulfillments and update fulfillment status. It will also handle making inventory adjustments on the warehouse and virtual sales channel locations when needed for complete, short, and backorder shipments.

Configuration

Install the Webhook - Process Shipments Playbook Step on a business using the app.

  1. Login to appx.channelape.io

  2. Install playbook step on business by going to this URL: https://appx.channelape.io/{{businessId}}/embed/playbook/steps/a970417b-bb48-47cc-b3ea-58ca5d9edbc1/install

    1. Replace businessId with the ID of the business you are using
  3. Ensure Synchronous is enabled

  4. Ensure Authenticated Callback is enabled

    1. This flag will use the provided authorization headers on the callbacks endpoint
  5. Set version to STAGING if this is a Staging Business otherwise use PRODUCTION

  6. Under Order Filtering you may optionally select 1 or many channels if you know that you will only be processing fulfillment order receipts for those channels. All other fields can be left alone since they are not used.

  7. Enter a valid value for your Virtual Sales Channel Location ID where inventory adjustments could be made based on fulfillment order receipt activity.

  8. Fill in the Warehouse Location Map with all of the locations you expect to receive fulfillment order receipts for.

  9. Select Install

  10. Refresh the Playbook ➝ Steps Configuration page and select the new step you just installed

  11. Select Copy Callback URL

    1. This URL is your endpoint to post fulfillment order receipts too
    2. You will need to pass in a valid authorization headers along with the payload
  12. Optionally select 1 or many channels on the Steps Configuration page

    1. Will help with additional filtering when receiving a payload. This is usually only needed if you wish to match a Purchase Order Number or Warehouse Order ID that is duplicated across channels. In this case you would select the single channel you are interested in receiving ship confirmations for.

Example

Given the following values:

[
{
"warehouse": "RJT1",
"company": "United Parcel Service",
"locationId": "942",
"name": "UPS East Coast",
"id": "UPSF",
"zipCode": "40165"
},
{
"warehouse": "RJT2",
"company": "United Parcel Service",
"locationId": "874",
"name": "UPS West Coast",
"id": "UPSW",
"zipCode": "91752"
}
]
  • Order with purchaseOrderNumber of CAT333 has 1 PENDING fulfillment with the following line items:
    • SKU sku-1, Quantity 2
    • SKU sku-2, Quantity 3

Request sent: POST https://callbacks.channelape.com/v1/suppliers/f81c87c7-2692-4088-a396-05872695ff93/callbacks

Headers sent:

Authorization: Bearer YOUR_KEY
apikey: ANON_KEY
Content-Type: application/json

Payload sent:

{
"orderUpdate": {
"fulfillments": [
{
"purchaseOrderNumber": "CAT333", // optional
"additionalFields": [
{
"name": "warehouse_order_id",
"value": "CAT333-3"
}
],
"locationId": "874",
"shippingCompany": "UPS",
"shippingMethod": "UPS GROUND",
"trackingNumber": "54545412165451",
"status": "OPEN",
"lineItems": [
{
"sku": "sku_1",
"quantity": 2
},
{
"sku": "sku_2",
"quantity": 3
}
]
}
]
}
}

Will return a 200 Response Status

And a Response Body of:

{
"updateResult": {
"inventoryAdjustmentCount": 2,
"inventorySetCount": 0,
"orderActivityCount": 0,
"orderUpdateCount": 1
}
}

And Order with purchaseOrderNumber of CAT333 has 1 OPEN fulfillment with the following line items: SKU sku-1, Quantity 2 SKU sku-2, Quantity 3

And the following inventory adjustments are made:

  • SKU sku-1, Location ID 874
    • AVAILABLE_TO_SELL Quantity -2
    • COMMITTED Quantity -2
  • SKU sku-2, Location ID 874
    • AVAILABLE_TO_SELL Quantity -3
    • COMMITTED Quantity -3

Workflow

Set the required authorization headers

Setup a flow in integration to map fulfillment order receipts to the expected JSON format. Send the JSON in the request body of a POST call to the URL you received in setup. The request body should look something like:

{
"orderUpdate": {
// id, channelOrderId, purchaseOrderNumber, or warehouse_order_id are required to match
"id": "order_id",
"channelOrderId": "channel_order_id",
"purchaseOrderNumber": "purchase_order_number",
"fulfillments": [
{
"id": "fulfillment_id", // optional
"additionalFields": [
{
"name": "warehouse_order_id", // optional as long as id, channelOrderId, or purchaseOrderNumber are included
"value": "warehouse_1"
},
{
"name": "some_extra_data", // optional
"value": "some_extra_data_value"
},
{
"name": "more_extra_data", // optional
"value": "more_extra_data"
}
],
"supplierId": "supplierIdValue", // optional
"shippingCompany": "UPS", // optional
"shippingMethod": "UPS GROUND", // optional
"trackingNumber": "54545412165451",
"trackingUrls": [
// optional
"https://example.tracking/445687484",
"https://other.tracking/4456874895355"
],
"shippedAt": "2022-08-10T19:46:05.014Z", // optional - defaults to current time
"locationId": "location_1", // maps to locationId within WAREHOUSE_LOCATION_MAP
"status": "OPEN", //Can be OPEN|SUCCESS|CANCELED|PENDING|NEW|BACKORDER by is typically OPEN for fulfillment order receipts coming back from the warehouse
"lineItems": [
{
//Supply either id or sku both are acceptable but generally stick with sku
"id": "linItem_id",
"sku": "sku_1",
"title": "sku_1 title", // optional
"additionalFields": [
// optional
{
"name": "some_extra_data",
"value": "some_extra_data_value"
}
],
"grams": 450, // optional
"price": 1500, // optional
"shippingPrice": 500, // optional
"shippingTax": 25, // optional
"quantity": 9,
"shippingMethod": "UPS GROUND", // optional
"vendor": "channel ape" // optional
}
]
}
]
}
}

The order id , channelOrderId , purchaseOrderNumber, or warehouse_order_id (found within fulfillment[x].additionalFields) are required to identify the order.

For fulfillments.lineItems an identifier to id or sku has to be defined and the quantity.

locationId should be mapped to a value within the WAREHOUSE_LOCATION_MAP. It will not attempt to change a fulfillment that's locationId is not in the current context.

Anything else on fulfillments and its descendants can be defined and it'll be merged in. Note that anything else on the parent order object won't be merged in and ignored. Don't attempt to do an order state transition or update the order lineItems.

If no result comes back a 204 Response Status is returned with an empty Response Body. If something is wrong with the input a 400 Response Status is returned with an error trying to explain what was wrong. If everything worked it will return a 200 Response Status with the following Response Body:

{
"updateResult": {
"inventoryAdjustmentCount": 1,
"inventorySetCount": 2,
"orderActivityCount": 3,
"orderUpdateCount": 4
}
}

The numbers will reflect the amount of results updated.

Partial Fulfillments

Orders are typically processed with a single fulfillment order receipt message. If items are under-fulfilled or absent from this message, we assume they are backordered. However, certain integrations necessitate ChannelApe to receive multiple ship confirmation messages for a single order fulfillment request. If this applies, enable the "partial fulfillments" feature to indicate that multiple messages per order fulfillment are permissible.

warning

If you enable partial fulfillments there are less protections for exceptional shipping cases like short shipments.

Config

To enable partial fulfillments just add the following to the body of the request:

{
"fulfillmentOptions": {
"partialShipments": true
},
...[rest of the request]
}

Example Workflow

Given the example order:

{
"id": "orderId_1",
"status": "IN_PROGRESS",
"purchaseOrderNumber": "purchase_1",
"fulfillments": [
{
"additionalFields": [
{
"name": "warehouse_order_id",
"value": "warehouse_1"
}
],
"trackingNumber": "3903289892389238932",
"locationId": "locationId_1",
"shippingCompany": "UPS",
"shippingMethod": "Ground",
"status": "PENDING",
"lineItems": [
{
"sku": "sku_1",
"quantity": 2
},
{
"sku": "sku_2",
"quantity": 3
}
]
}
]
}

Sending the following partial fulfillment:

{
"fulfillmentOptions": {
"partialShipments": true
},
"orderUpdate": {
"purchaseOrderNumber": "purchase_1",
"fulfillments": [
{
"additionalFields": [
{
"name": "warehouse_order_id",
"value": "warehouse_1"
}
],
"status": "OPEN",
"locationId": "locationId_1",
"lineItems": [
{
"quantity": 2,
"sku": "sku_1"
}
]
}
]
}
}

Will return the following result:

{
"updateResult": {
"inventoryAdjustmentCount": 2,
"inventorySetCount": 0,
"orderActivityCount": 0,
"orderUpdateCount": 1
}
}

The state of the order after this shipment has happened will be as follows:

{
"id": "orderId_1",
"status": "IN_PROGRESS",
"purchaseOrderNumber": "purchase_1",
"fulfillments": [
{
"additionalFields": [
{
"name": "warehouse_order_id",
"value": "warehouse_1"
}
],
"trackingNumber": "3903289892389238932",
"locationId": "locationId_1",
"shippingCompany": "UPS",
"shippingMethod": "Ground",
"status": "OPEN",
"lineItems": [
{
"sku": "sku_1",
"quantity": 2
}
]
},
{
"additionalFields": [
{
"name": "warehouse_order_id",
"value": "warehouse_1"
}
],
"trackingNumber": "3903289892389238932",
"locationId": "locationId_1",
"shippingCompany": "UPS",
"shippingMethod": "Ground",
"status": "PENDING",
"lineItems": [
{
"sku": "sku_2",
"quantity": 3
}
]
}
]
}

A second process shipment call can then be made to fulfill the remaining fulfillment.