Channels are fulfillment platforms where Openship can automatically purchase products to fulfill orders. Unlike shops (which sell products), channels provide products for purchase when orders need to be fulfilled. Openship has a built-in integration for Shopify so new integrations can be built by following the same pattern.
The Shopify channel integration (/features/integrations/channel/shopify.ts) demonstrates all required functions for a complete channel integration. This guide will explain how each function works and how to implement it for your own channel integration. Each function below has 3 parts, what Openship sends to the function, how the function works, and what Openship expects to be returned.
Purpose: Openship uses this function to search for products available for purchase when setting up product matches, allowing users to find fulfillment options for their shop products
Purpose: Openship uses this function to fetch detailed product information from channel platforms when creating product matches, ensuring accurate pricing and availability data for automated purchasing
Purpose: Openship uses this function to automatically create purchases on channel platforms when orders are received from connected shops, enabling automated order fulfillment workflow
Request Body:
Prop
Type
Default
platform
{ domain: string; accessToken: string }
-
cartItems
CartItem[]
-
shipping?
ShippingAddress
-
notes?
string
-
Response Format:
Field
Type
Description
purchaseId
string
Unique identifier for the purchase
orderNumber
string
Human-readable order number
totalPrice
string
Total cost of the purchase
invoiceUrl
string
URL to the purchase invoice
lineItems
LineItem[]
Items included in the purchase
status
string
Current status of the purchase
export async function createPurchaseFunction({ platform, cartItems, shipping, notes,}: { platform: { domain: string; accessToken: string }; cartItems: Array<{ variantId: string; quantity: number; price?: string; }>; shipping?: { firstName: string; lastName: string; address1: string; address2?: string; city: string; province: string; country: string; zip: string; phone?: string; }; notes?: string;}) { const shopifyClient = new GraphQLClient( `https://${platform.domain}/admin/api/graphql.json`, { headers: { "X-Shopify-Access-Token": platform.accessToken, }, } ); // Create draft order const mutation = gql` mutation CreateDraftOrder($input: DraftOrderInput!) { draftOrderCreate(input: $input) { draftOrder { id name invoiceUrl totalPrice lineItems(first: 50) { edges { node { id title quantity originalUnitPrice variant { id title product { id title } } } } } } userErrors { field message } } } `; const lineItems = cartItems.map(item => ({ variantId: `gid://shopify/ProductVariant/${item.variantId}`, quantity: item.quantity, originalUnitPrice: item.price, })); const input: any = { lineItems, note: notes, }; if (shipping) { input.shippingAddress = { firstName: shipping.firstName, lastName: shipping.lastName, address1: shipping.address1, address2: shipping.address2, city: shipping.city, province: shipping.province, country: shipping.country, zip: shipping.zip, phone: shipping.phone, }; } const result = await shopifyClient.request(mutation, { input }); if (result.draftOrderCreate.userErrors.length > 0) { throw new Error(`Failed to create purchase: ${result.draftOrderCreate.userErrors.map(e => e.message).join(', ')}`); } const draftOrder = result.draftOrderCreate.draftOrder; // Complete the draft order const completeMutation = gql` mutation CompleteDraftOrder($id: ID!) { draftOrderComplete(id: $id) { draftOrder { order { id name totalPrice lineItems(first: 50) { edges { node { id title quantity variant { id } } } } } } userErrors { field message } } } `; const completeResult = await shopifyClient.request(completeMutation, { id: draftOrder.id, }); if (completeResult.draftOrderComplete.userErrors.length > 0) { throw new Error(`Failed to complete purchase: ${completeResult.draftOrderComplete.userErrors.map(e => e.message).join(', ')}`); } const order = completeResult.draftOrderComplete.draftOrder.order; return { purchaseId: order.id.split("/").pop(), orderNumber: order.name, totalPrice: order.totalPrice, invoiceUrl: draftOrder.invoiceUrl, lineItems: order.lineItems.edges.map(({ node }) => ({ id: node.id.split("/").pop(), title: node.title, quantity: node.quantity, variantId: node.variant.id.split("/").pop(), })), status: "pending", };}
Purpose: Openship uses this function to set up real-time webhooks that notify the system when purchases are fulfilled or cancelled on channel platforms, enabling automatic tracking updates to customers
If you have a Shopify app (or app on any platform), you can implement OAuth functions to allow users to install your app directly instead of manually retrieving access tokens. This provides a smoother user experience where users can authorize your integration through the standard app installation flow.
Purpose: Openship uses this function to generate secure OAuth authorization URLs that allow users to safely connect their channel platforms to Openship for automated purchasing
Purpose: Openship uses this function to complete the OAuth flow by exchanging temporary authorization codes for permanent access tokens, establishing a secure connection to the user's channel platform
Purpose: Openship uses this webhook handler to receive tracking information when purchases are shipped from channel platforms, automatically forwarding tracking details to customers and updating order status
Purpose: Openship uses this webhook handler to process purchase cancellation events from channel platforms, automatically handling refunds and updating order status across connected platforms
Request Body:
Prop
Type
Default
platform
{ domain: string; accessToken: string }
-
event
any
-
headers
Record<string, string>
-
Response Format:
Field
Type
Description
order
Order
Cancelled purchase object with cancellation details
Once you've implemented your channel integration functions, you need to create a channel platform in Openship. Here's exactly what you'll see in the Openship interface:
Now you can click "Create Channel" to create individual channel instances that use your platform template. Each channel will use the same integration logic but with different credentials (domain, access tokens, etc.).
The platform system ensures your integration functions are reusable across multiple channel instances while maintaining clean separation between template logic and instance-specific configurations.