API

Connecting a custom channel

Connect your custom sales channel to Jetti to sync orders, shipments and products

Last updated July 9th, 2020

Although Jetti connects to a number of hosted and open source Ecommerce platforms, you can also connect Jetti to custom built systems. As a pre-requisite, we'd recommend you reading through our support articles on getting started with the Jetti API.

Alongside this guide, you can also access a GitHub repository with sample implementations of many of the endpoints and API endpoints covered in this guide.

Finally, we understand each custom build is different. This guide is intended to provide a high level overview of the data flows / integration points. Where needed, we can provide dedicated architecture and alternative implementations if required.

Step 1

Create a new custom channel

You'll first need to create a new custom channel under the integrations section. Jetti allows you to integrate multiple custom channels under a single Jetti account if needed.

Step 2

Setup the inventory webhook

When connecting a custom channel, inventory can be synced in real time. As Jetti is the stock master, inventory updates are sent from Jetti up to your custom channel when an inventory related event occurs. For example, a new order or the available levels in an inventory feed changes.

You can set up the webhook endpoint under the Order processing tab in the custom channel setup. This should be a http:// or https:// endpoint.

The outbound JSON payload will contain details on the variant, the channel variant and the available inventory levels. The available quantity is the amount currently available to be sold. Although this may change depending on your account setup, by default it will be the amount you have in your warehouse (or your inventory feed), with any allocated/unshipped quantities removed.

{
  "variant": {
    "id": 10,
    "inventoryPolicy": "track"
  },
  "channelVariant": {
    "id": 9,
    "companyId": 11,
    "variantId": 10,
    "channelId": 21,
    "externalId": "123",
    "externalGroupId": null,
    "externalSku": null,
    "source": "shopify",
    "hash": null,
    "parentHash": null,
    "status": "pending",
    "errorMessage": null,
    "inventoryUpdate": null,
    "priceUpdate": null,
    "publishUpdate": null,
    "costPriceUpdate": null,
    "createdAt": "2019-12-16T21:45:12.828Z",
    "updatedAt": "2019-12-16T21:45:12.828Z",
    "channel": {
      "shipstationUrl": "http://localhost:5000/api/channels/21/shipstation.xml",
      "freeShippingThreshold": null,
      "hideWhenNoCostPrice": null,
      "id": 21,
      "includeBackorderQuantity": true,
      "inventorySync": "all",
      "parentChannel": "custom",
      "unpublishNoInventory": false,
      "warehouseId": 11,
      "warehouseSync": "default"
    }
  },
  "inventory": {
    "available": 5
  }
}

The response from the webhook should be received within 5 seconds, although preferably 1 second. Outbound updates can also be sent concurrently, so we advise a queue/messaging system to cater for peaks in throughput and ensure minimal response times.

Step 3

Setup the pricing webhook

As with inventory, pricing can also be synced in realtime via webhooks. First, you'll need to configure the location of the endpoint under your custom channel settings. This will typically be the location of a route in your custom store. Or, the location of a serverless function designed in process parallel requests at scale.

Once setup, Jetti will push out pricing updates in real time as prices change. For example, Jetti will send a request to the endpoint if the pricing of an item changes within an inventory feed. The webhook sends a payload of data via a POST request containing details of the price (including the compare at price), the variant and the channel variant.

{
    "channelVariant": {
        "id": 9,
        "companyId": 11,
        "variantId": 10,
        "channelId": 21,
        "externalId": "123",
        "externalGroupId": null,
        "externalSku": null,
        "source": "shopify",
        "hash": null,
        "parentHash": null,
        "status": "pending",
        "errorMessage": null,
        "inventoryUpdate": {
            "inventoryQuantity": 5
        },
        "priceUpdate": null,
        "publishUpdate": null,
        "costPriceUpdate": null,
        "createdAt": "2019-12-16T21:45:12.828Z",
        "updatedAt": "2019-12-16T21:45:13.192Z",
        "channel": {
            "id": 21,
            "parentChannel": "custom",
            "priceListId": 11,
            "name": "Custom",
            "syncPricing": true
        }
    },
    "price": {
        "price": 10,
        "compareAtPrice": null,
        "id": 5,
        "companyId": 11,
        "variantId": 10,
        "priceListId": 11,
        "updatedAt": "2019-12-16T21:45:13.197Z",
        "createdAt": "2019-12-16T21:45:13.197Z"
    },
    "zeroCompareAtPrices": null
}

The endpoint should return a 200 response within 3 seconds of the API request. Although this can be extended if needed, we recommend actions on your custom channel are asynchronous. An example setup would typically push the payload of data into a background queue for later processing.

Step 4

Setup the webhook for fulfillments

Fulfillment notification updates can also be sent to your custom channel in real time. Although you can listed to new shipments through our regular webhooks, we recommend using the webhook under the custom channel setup page. This webhook will bundle additional details on the shipments, such as more information on the line items which would otherwise take additional API lookups to fetch.

Step 5

Importing orders

We recommend using the manual-sale.json endpoint for importing orders, rather than combining POST requests across a number of different endpoints to construct and process the sale in Jetti.

The POST https://api.jetti.io/api/channels/:id/manual-sale.json endpoint allows you to bundle the line item, customer and order details into a single API payload. You can also use this payload for both sending updates about an existing orders, as well as sending through the order for the first time.

{
    "externalId": "1234",
    "saleDefaults": {},
    "customer": {
        "firstName": "Test",
        "lastName": "user",
        "email": "test@user.com"
    },
    "lineItems": [{
        "externalId": "line-external-id",
        "name": "line-item-name",
        "quantity": 10,
        "price": 20,
        "taxable": true,
        "variant": {
            "externalId": "123"
        },
        "properties": [{
            "key": "property-key",
            "value": "property-value"
        }]
    }],
    "shipping": [{
        "code": "shipping-code",
        "name": "shipping-name",
        "serviceLevel": "shipping-service-level",
        "price": 10
    }]
}

The endpoint returns a sales object - you can find the details of that within our API reference. You can also use the JSON schema within the sales resource to populate the saleDefaults object in the manual sale endpoint. Typically, this will be used to populate the shipping and billing addresses for the order.

The line items should be an array, detailing the item, price, quantity etc. The externalId should be the external line item ID for the object. Typically this would the the database ID for the order item within your custom system. If you want to pass in custom data for the line item, such as engraving or extended product options, you can use the properties endpoint. These values will be passed to compatible vendor systems (such as Shopify and WooCommerce) and will also be visible within the vendor portal.

The variant object connects the line item to an item within Jetti. You'll need to either pass an externalId (the external unique system ID for the item in your custom system) or the SKU for the item.

Additional options are available when importing orders, such as importing items dynamically from an inventory feed. Or, updating the variant details in Jetti with the payload used to create the order (e.g. if you wanted to update the variant with a new weight.

Shipping can either be passed in an a single object or as an array of objects (e.g. if there are multiple shipping lines to import).

Step 6

Pulling data feeds from Jetti

To avoid constructing complex queries to filter attributes and side load additional resources, you can make use of the scope parameter in the API to pull in product and vendor related information.

To pull a list of products along with their corresponding variants, options and inventory feeds, you can poll GET https://api.jetti.io/api/products.json?scope=channel.

[{
    "id": 977535,
    "companyId": 1235,
    "name": "Gore Gore® X7 Partial Gore-tex Infinium™ Jacket - Blue",
    "handle": "gore-gore-x7-partial-gore-tex-infinium-jacket-blue",
    "vendor": null,
    "createdAt": "2019-11-22T20:56:57.564Z",
    "updatedAt": "2019-11-22T20:56:57.564Z",
    "variants": [{
        "imagesMapped": [],
        "images": [],
        "lowInventoryQuantity": true,
        "displayName": "100537ARAP06",
        "costPrice": 0,
        "id": 3099498,
        "companyId": 1235,
        "productId": 977535,
        "vendorId": 37465,
        "dropshipProviderId": null,
        "sku": "100537ARAP06",
        "name": "X-Large",
        "dropshipProviderMapping": "brand_name",
        "description": "<p>The ski-specific fit of this jacket will allow you unhindered movement while performing your best. Enough GORE-TEX INFINIUM™ material to keep you protected, without the bulk of a heavier jacket.</p>",
        "productType": "",
        "requiresShipping": true,
        "barcode": "",
        "levels": null,
        "inventoryAllocated": 0,
        "inventoryQuantity": 0,
        "inventoryFeeds": 0,
        "minInventoryQuantity": 0,
        "defaultPurchaseQuantity": 1,
        "leadTime": null,
        "totalPurchaseItems": 0,
        "totalSaleItems": 0,
        "grams": 0,
        "inventoryPolicy": "track",
        "inventoryType": "tracked",
        "position": 5,
        "taxable": true,
        "serialPrefix": null,
        "serialSuffix": null,
        "serialReference": 0,
        "vendorSku": null,
        "fulfillmentPolicy": "vendor_default",
        "noInventoryPolicy": "replenish",
        "automaticallyBackorder": "vendor_default",
        "dynamicInventoryPolicy": null,
        "denyThreshold": 0,
        "backorderQuantity": 0,
        "tags": [
            "Mix and Match_Apply Offer"
        ],
        "notes": null,
        "packingHeight": 0,
        "packingWidth": 0,
        "packingDepth": 0,
        "distanceUnit": "in",
        "inventoryRouting": "inventory_feed",
        "createdAt": "2019-11-20T03:36:08.215Z",
        "updatedAt": "2019-12-04T14:27:17.355Z",
        "vendor": {
            "id": 37465,
            "name": "GORE",
            "fulfillmentPolicy": "company_default"
        },
        "option_values": [{
            "id": 8439516,
            "variantId": 3099498,
            "optionId": 1289956,
            "variantValue": "X-Large",
            "option": {
                "id": 1289956,
                "name": "Size"
            }
        }],
        "prices": [{
            "price": 219,
            "id": 3652272,
            "priceListId": 1645
        }],
        "channel_variants": [{
            "id": 3690999,
            "variantId": 3099498,
            "channelId": 1858,
            "status": "connected",
            "externalId": "31293837639764",
            "externalSku": "100537ARAP06"
        }]
    }]
}]

The endpoint supports the full pagination and additional querying features of our API. For example, you can use this to paginate through the complete list of products to export to your store. Details on the JSON schemas for the above data flows can be found within our API reference.

Back to API