EDI Ecommerce Integration
Published · May 2026 Read time · 14 min Standard · X12 850 / EDIFACT ORDERS Platform · Shopify example
Trading Partner
Retailer / 3PL
850 / ORDERS
855 / ORDRSP
EDI Middleware
VAN / iPaaS
JSON / API
Your Store
Shopify
PURCHASE ORDER FLOW · X12 850 → SHOPIFY ORDER

Electronic Data Interchange (EDI) has been the backbone of B2B commerce for decades. Yet most ecommerce teams treat it as a black box — something their 3PL or ERP "just handles." That mindset breaks the moment you onboard a new retail partner, scale past 500 daily orders, or try to automate your supply chain.

This guide walks you through the complete integration process: from understanding what EDI documents actually contain, to mapping them into Shopify, to avoiding the production-breaking mistakes that trip up 80% of first-time integrations. We'll use X12 850 (North America) and EDIFACT ORDERS (international) as our primary examples throughout.

Who this is for: ecommerce engineers, integration architects, and technical operations leads who need to connect a trading partner's EDI feed to a modern commerce platform — without hiring a legacy systems consultant.

WHAT EDI ACTUALLY IS IN 2026

EDI is a standardized electronic format for exchanging business documents — purchase orders, invoices, shipment notices, and more — between companies. It predates the internet and still processes trillions of dollars in commerce annually. The key standards you'll encounter:

Standard Region PO Document PO Acknowledgment ASN / Ship Notice Invoice
ANSI X12 North America 850 855 856 810
EDIFACT International / EU ORDERS ORDRSP DESADV INVOIC
TRADACOMS UK Retail ORDER ORDHDR DLCDET INVFIL

For Shopify integrations, you'll almost always be on the receiving end of an 850/ORDERS (a retailer sends you a PO) and the sending end of an 856/DESADV (you send them your shipment notice) and 810/INVOIC (you send them an invoice). Understanding this directionality is critical before you write a single line of code.

Anatomy of an X12 850 (Purchase Order)

An 850 is a flat, delimiter-separated file. It looks nothing like JSON. Each line is a segment, and each segment contains fields separated by an asterisk (by default). Here's a simplified real-world example:

X12 850 · Purchase Order (abbreviated)
ISA*00*          *00*          *ZZ*RETAILER       *ZZ*YOURCOMPANY    *260501*0900*^*00501*000000001*0*P*:
GS*PO*RETAILER*YOURCOMPANY*20260501*0900*1*X*005010
ST*850*0001
BEG*00*SA*PO-98765**20260501      ~ Purchase order number + date
REF*DP*DEPT-42               ~ Department number
DTM*002*20260515             ~ Required delivery date
N1*ST*Ship-To Warehouse*92*WH-001     ~ Ship-to party
N3*123 Commerce Blvd
N4*Austin*TX*78701*US
PO1*1*24*EA*29.99*TE*VN*SKU-ABC123   ~ Line 1: qty 24, unit price $29.99
PID*F****Blue Widget 500ml
PO1*2*12*EA*49.99*TE*VN*SKU-DEF456   ~ Line 2: qty 12, unit price $49.99
PID*F****Red Widget Pro
CTT*2                         ~ 2 line items total
SE*12*0001
GE*1*1
IEA*1*000000001
Key insight: The PO1 segments are your order line items. Each contains quantity, unit of measure, unit price, and a vendor-specific product identifier (your SKU). This maps directly to Shopify's line_items array in the Orders API.

Anatomy of an EDIFACT ORDERS

EDIFACT uses a different delimiter convention (plus signs and apostrophes by default) but carries the same semantic payload:

EDIFACT ORDERS · Purchase Order (abbreviated)
UNB+UNOA:1+RETAILER+YOURCOMPANY+260501:0900+00001'
UNH+1+ORDERS:D:96A:UN:EAN008'
BGM+220+PO-98765+9'           ← document type + PO number + original
DTM+137:20260501:102'         ← document date
DTM+2:20260515:102'           ← delivery date
NAD+ST+WH-001::92+Ship-To Warehouse+123 Commerce Blvd+Austin+TX+78701+US'
LIN+1++SKU-ABC123:SA'         ← line 1 with seller's article number
IMD+F++:::Blue Widget 500ml'
QTY+21:24'                    ← ordered quantity: 24
PRI+AAA:29.99'               ← unit price: 29.99
LIN+2++SKU-DEF456:SA'
IMD+F++:::Red Widget Pro'
QTY+21:12'
PRI+AAA:49.99'
UNS+S'
CNT+2:2'
UNT+18+1'
UNZ+1+00001'

STEP-BY-STEP INTEGRATION

The following process is proven across hundreds of B2B ecommerce deployments. Follow it in order — skipping steps is the single biggest cause of failed go-lives.

1

Collect Your Trading Partner's Specs (Implementation Guide)

Every retailer sends you an EDI Implementation Guide (IG) — a PDF specifying exactly which segments, qualifiers, and codes they require. Never skip this. Even if you know X12 850 cold, Target's IG is different from Walmart's, which is different from Costco's.

Key items to extract from the IG before touching any code:

  • Required vs. optional segments and elements
  • Their ISA/GS qualifier codes (your sender ID)
  • Product identifier qualifiers (VN = vendor item, BP = buyer's part number, etc.)
  • Date format requirements (YYYYMMDD vs YYMMDD)
  • Quantity/UOM codes they accept (EA, CA, DZ, etc.)
  • Required acknowledgment SLA (usually 855 within 24 hours of receiving an 850)
2

Choose Your Integration Architecture

There are three viable approaches for connecting EDI to Shopify. Each has a real tradeoff:

Approach Best For Typical Cost Setup Time
Managed VAN
(SPS Commerce, TrueCommerce, DiCentral)
Teams with no EDI expertise; retailer mandates specific VAN $500–$3k/month 2–6 weeks
iPaaS / Integration Platform
(Celigo, Boomi, MuleSoft, Workato)
Engineering teams wanting control + pre-built Shopify connectors $1k–$5k/month 3–8 weeks
Custom EDI Parser + Shopify API
(node-x12, pyx12, python-edifact)
High-volume teams needing full control and cost efficiency Dev time only 6–16 weeks
Warning: If your trading partner mandates a specific VAN (very common with Walmart, Target, and major grocery chains), you may not have a choice. Check their IG for connectivity requirements before selecting an architecture.
3

Set Up File Transport (AS2, SFTP, or API)

EDI files need a secure transport mechanism. AS2 (Applicability Statement 2) is the industry standard for direct connections and is required by most large retailers. SFTP is common for smaller partners or internal tools.

AS2 connection config example (Mirth Connect / Drummond-certified)
{
  "as2Id": "YOURCOMPANY",
  "partnerId": "RETAILER",
  "url": "https://as2.retailer.com:4080/as2",
  "encryption": "AES-256-CBC",
  "signing": "SHA-256",
  "mdn": {
    "type": "sync",          // request synchronous MDN
    "signed": true
  },
  "certificates": {
    "your_private": "./certs/yourcompany.p12",
    "partner_public": "./certs/retailer.cer"
  }
}

Certificate exchange happens out-of-band — you'll email your public certificate to your partner's EDI team and receive theirs. Budget a full week for this process with large retailers due to their security review cycles.

4

Parse the EDI Document and Map Fields to Shopify

This is where the real engineering work happens. You need to extract every relevant field from the 850/ORDERS and map it to the Shopify Orders API. Here's a complete field mapping:

X12 850 Segment/Element EDIFACT ORDERS Shopify Field Notes
BEG03 (PO Number) BGM02 order.name / tags Store as tag for searchability
DTM02 (Delivery Date) DTM+2 order.note_attributes No native Shopify field; use metafield
N1*ST (Ship-To) NAD+ST order.shipping_address Map N3/N4 to address lines
N1*BT (Bill-To) NAD+BY order.billing_address Often same as ship-to in B2B
PO1*02 (Quantity) QTY+21 line_items[].quantity Watch UOM — convert CS→EA if needed
PO1*04 (Unit Price) PRI+AAA line_items[].price May differ from your Shopify price list
PO1*07 (SKU, qualifier VN) LIN+SA line_items[].sku Match to Shopify variant.sku exactly
REF*DP (Dept. Number) RFF+DP order.note_attributes Required for some retailer invoicing
Node.js · Parse X12 850 → Shopify Order payload
import { X12Parser } from 'node-x12';

async function edi850ToShopifyOrder(rawEdiString) {
  const parser = new X12Parser(true);
  const interchange = parser.parse(rawEdiString);
  const group = interchange.functionalGroups[0];
  const transaction = group.transactions[0]; // 850

  // Extract PO number from BEG segment, element 03
  const poNumber = transaction.get('BEG03');

  // Extract ship-to address from N1/N3/N4 loop
  const n1Loop = transaction.getLoop('N1', 'ST');
  const shippingAddress = {
    address1: n1Loop.get('N301'),
    address2: n1Loop.get('N302') || '',
    city:     n1Loop.get('N401'),
    province: n1Loop.get('N402'),
    zip:      n1Loop.get('N403'),
    country:  n1Loop.get('N404'),
  };

  // Extract line items from PO1 loops
  const lineItems = transaction.getLoops('PO1').map(po1 => ({
    sku:      po1.get('PO107'),         // VN qualifier = vendor SKU
    quantity: parseInt(po1.get('PO102')),
    price:    parseFloat(po1.get('PO104')),
    title:    po1.get('PID05') || '',   // from PID segment in loop
  }));

  // Resolve Shopify variant IDs from SKUs
  const resolvedItems = await Promise.all(
    lineItems.map(async item => {
      const variantId = await lookupVariantBySku(item.sku);
      if (!variantId) throw new Error(`SKU not found: ${item.sku}`);
      return { variant_id: variantId, quantity: item.quantity, price: item.price };
    })
  );

  return {
    order: {
      line_items: resolvedItems,
      shipping_address: shippingAddress,
      tags: [`edi-po:${poNumber}`, 'edi-order'],
      note_attributes: [
        { name: 'edi_po_number', value: poNumber },
        { name: 'edi_source', value: 'X12-850' },
      ],
      financial_status: 'pending',
    }
  };
}
5

Post the Order to Shopify via Admin API

With your mapped payload ready, post it to Shopify's Admin REST or GraphQL API. For B2B orders, use the send_receipt: false flag to suppress the customer-facing confirmation email (your trading partner expects EDI acknowledgments, not email).

Shopify Admin API · Create order from EDI payload
const response = await fetch(
  `https://${SHOP}.myshopify.com/admin/api/2024-04/orders.json`,
  {
    method: 'POST',
    headers: {
      'X-Shopify-Access-Token': ACCESS_TOKEN,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      ...shopifyOrderPayload,
      send_receipt: false,      // suppress email to partner
      send_fulfillment_receipt: false,
      inventory_behaviour: 'decrement_obeying_policy',
    }),
  }
);

if (!response.ok) {
  const error = await response.json();
  // Log full error — Shopify returns field-level validation errors
  throw new Error(`Shopify order creation failed: ${JSON.stringify(error)}`);
}

const { order } = await response.json();
// Store order.id → poNumber mapping in your database for 855/856 generation
await savePoMapping({ shopifyOrderId: order.id, poNumber, partnerId });
6

Send the 855 / ORDRSP Acknowledgment

Most trading partners require an 855 Purchase Order Acknowledgment within 24–48 hours. This document tells the retailer you received their PO and whether you accept it in full, partially, or reject it. Missing the SLA on this is a chargeback risk.

X12 855 · Purchase Order Acknowledgment (minimal)
ISA*00*          *00*          *ZZ*YOURCOMPANY    *ZZ*RETAILER       *260501*1000*^*00501*000000002*0*P*:
GS*PR*YOURCOMPANY*RETAILER*20260501*1000*2*X*005010
ST*855*0001
BAK*00*AC*PO-98765*20260501  ~ AC = Accepted, references original PO#
PO1*1*24*EA*29.99*VN*SKU-ABC123
ACK*IA*24*EA*202*20260515     ~ IA = Item Accepted
PO1*2*12*EA*49.99*VN*SKU-DEF456
ACK*IA*12*EA*202*20260515
CTT*2
SE*9*0001
GE*1*2
IEA*1*000000002

The BAK02 element is your acceptance code. AC = fully accepted. AD = accepted with changes. RJ = rejected. If you change quantities or dates, you must use AD and document exactly what changed on each PO1 line.

7

Automate the 856 ASN on Fulfillment

When Shopify marks an order as fulfilled (via webhook orders/fulfilled), your integration must immediately generate and transmit an 856 Advance Ship Notice. The ASN must include tracking numbers, carrier codes, and exact carton-level detail for many retailers. This document is time-sensitive — many partners require ASN receipt before the physical shipment arrives.

Shopify webhook → 856 generation (pseudo-code)
// Register webhook in Shopify
POST /admin/api/2024-04/webhooks.json
{
  "topic": "orders/fulfilled",
  "address": "https://your-integration.com/webhooks/fulfillment",
  "format": "json"
}

// In your webhook handler:
async function handleFulfillment(shopifyOrder, fulfillment) {
  const poMapping = await getPoMapping(shopifyOrder.id);

  const asnData = {
    shipmentId:  `ASN-${Date.now()}`,
    poNumber:    poMapping.poNumber,
    shipDate:    fulfillment.created_at,
    carrier:     mapCarrierCode(fulfillment.tracking_company),
    trackingNum: fulfillment.tracking_number,
    lines:       fulfillment.line_items.map(li => ({
      sku: li.sku, quantity: li.quantity,
    })),
  };

  const edi856 = generate856(asnData, poMapping.partnerId);
  await transmitViaAS2(edi856, poMapping.partnerId);
}
8

Set Up Monitoring, Alerting, and Reconciliation

EDI integrations fail silently in ways that cost real money. A malformed 850 that never creates a Shopify order is invisible unless you're actively monitoring. At minimum you need:

  • Transaction logging with full raw document storage (7-year retention for most retail partners)
  • Dead-letter queue for failed parsing with Slack/PagerDuty alert
  • Daily reconciliation job: compare EDI PO count vs Shopify orders created in same window
  • MDN (Message Disposition Notification) parsing to confirm AS2 delivery
  • ISA control number sequence monitoring (gaps indicate missing files)

MISTAKES THAT BREAK PROD

These are the errors that appear on every post-mortem after a failed EDI go-live. Most are invisible in testing and catastrophic at scale.

⚠️

Using Shopify Product ID instead of SKU for lookups

Shopify variant IDs change when products are deleted and recreated. Trading partners only know your SKU. Build your lookup table on sku, never on ID.

Index on variant.sku, refresh nightly, handle duplicates explicitly
⚠️

Ignoring the ISA control number envelope

Control numbers must be unique and sequential per trading partner. Reusing them causes partner systems to reject your transmissions as duplicates — silently.

Use a DB sequence per partner, never a timestamp or random number
⚠️

Not handling UOM conversion (cases vs each)

A PO for 5 CA (cases) of 12 units is 60 units in Shopify. Getting this wrong overfulfills or underfulfills every order. It also breaks your ASN quantities.

Build a UOM conversion table keyed to your item master. Never assume EA.
⚠️

Missing the 855 SLA window

Retail partners charge back $50–$500 per late or missing PO acknowledgment. Many require the 855 within 24 hours — not business hours, calendar hours.

Generate and send the 855 immediately on 850 receipt, before Shopify order creation
⚠️

Hardcoding the partner's qualifier values

N1 loops use qualifier codes that differ per partner (92 vs 9 vs UL for location identifiers). Hardcoding one causes misrouting when you add partner #2.

Store qualifiers in a per-partner config table, look them up at parse time
⚠️

Testing against prod with real PO numbers

Sending a test 855 with a real PO number triggers your partner's fulfillment workflows. Two teams learned this via unexpected shipments to test warehouses.

Always use partner-designated test ISA qualifier (usually "T" not "P" in ISA15)
⚠️

No idempotency on order creation

AS2 retries, network timeouts, and webhook replays can cause the same 850 to be processed twice — creating duplicate Shopify orders for the same PO.

Store processed ISA control numbers; check before creating any Shopify order
⚠️

Assuming the EDI spec matches the actual files

Trading partners send files that violate their own IGs. Optional segments are sometimes present, required segments are sometimes missing. Your parser must be defensive.

Parse leniently, validate semantically, alert on deviations rather than hard failing

GO-LIVE CHECKLIST

Use this before every new trading partner activation. Copy it to your project management tool and check off each item with a linked artifact (screenshot, config file, or test result).

📋 Pre-Integration

  • Obtained and fully read the partner's EDI Implementation Guide
  • Identified all required transaction sets (850, 855, 856, 810 at minimum)
  • Confirmed transport method: AS2, SFTP, or VAN portal
  • Exchanged AS2 certificates and tested connection handshake
  • Confirmed ISA sender/receiver qualifier codes with partner EDI team
  • Identified product identifier qualifier (VN, BP, IN, UP, etc.)
  • Mapped all partner SKUs / GTINs to Shopify variant SKUs
  • Confirmed UOM codes and built conversion table if needed

⚙️ Development

  • EDI parser handles all required segments per IG
  • Field mapping validated against 5+ real sample files from partner
  • Shopify order creation tested with send_receipt: false
  • ISA control number sequence implemented per-partner in persistent storage
  • Idempotency check on control number before order creation
  • 855 generation tested and validated against partner IG
  • 856 generation triggered by Shopify orders/fulfilled webhook
  • 810 invoice generation triggered by order shipment or configurable event
  • All documents pass partner's EDI validator tool (if provided)
  • Error handling: malformed segments, unknown SKUs, missing required fields

🧪 Testing

  • ISA15 set to "T" (test) — confirmed with partner before any test transmission
  • Sent and received all required transaction sets in partner test environment
  • Verified MDN receipts are being parsed and stored
  • Tested partial acceptance (AD) on 855 — partner system handled correctly
  • Tested rejection (RJ) on 855 — confirmed partner's downstream behavior
  • Ran duplicate PO test — confirmed idempotency logic blocked second order
  • Tested high volume: 100+ PO lines in a single 850 transaction set
  • Partner's EDI team signed off on all test transactions (get this in writing)

🚀 Go-Live & Ops

  • ISA15 flipped to "P" (production) — confirmed in code and partner portal
  • Monitoring dashboard live with transaction volume, error rate, SLA countdown
  • Alert configured for any failed transmission within 15 minutes of failure
  • Alert configured for control number gaps (missing files)
  • Daily reconciliation job scheduled and tested
  • Raw EDI document archive configured (min 7-year retention)
  • Runbook documented: how to manually replay a failed 850
  • Chargeback risk register created with partner's penalty schedule
  • On-call rotation established for first 30 days post-launch

THE INTEGRATION IS NEVER DONE

EDI integration is not a feature you ship once. Trading partners update their IGs. Retailers change their chargeback policies. New document types get added (functional acknowledgments, 997s, PO changes via 860s). Shopify's API version ticks forward. Your item master diverges from your partner's records.

Build your integration as a maintainable service, not a script. Keep your field mappings in configuration rather than code. Version your IG interpretations. Log everything. The teams that treat EDI as infrastructure — rather than a one-time project — are the ones that onboard their fifth retail partner as smoothly as their first.

Quick recap: Collect the IG → Choose your architecture → Set up AS2/SFTP → Parse 850/ORDERS → Map to Shopify → Send 855 within SLA → Automate 856 on fulfillment → Monitor, reconcile, and alert. That's the full loop.

Ready to go live?

Stop debugging EDI at midnight.
Let us handle the integration.

We've done this exact stack — 850 to Shopify, AS2, 855/856 automation — for ecommerce brands scaling into retail. One call to scope it, no legacy consultants.

Book a free scoping call →
Molten Logistics