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.
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:
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
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:
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.
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)
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 |
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.
{
"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.
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 |
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', } }; }
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).
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 });
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.
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.
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.
// 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); }
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.
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.
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.
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.
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.
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.
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.
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.
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/fulfilledwebhook - 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.
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 →