How to Transform a Shopify Order Webhook into a Clean Record

A Shopify orders/create webhook is one of the largest, most deeply nested payloads a developer regularly has to deal with. It carries customer data, billing and shipping addresses, an array of line items, money fields stored as strings, tax lines, discounts, and dozens of fields you will never use. Getting the three or four pieces you actually need out of it — into a database row, an invoice, or a fulfillment request — usually means writing and maintaining a transformation function. There is a faster way: show what the payload looks like and what you need it to become, and let a rule engine infer the mapping. This guide walks through the common Shopify reshaping jobs with real payload shapes.

Why Shopify payloads are painful to map

The Shopify Admin API and its webhooks are built to describe everything about an order, which is exactly why they are awkward to consume.

The total you want is a string, not a number: "total_price": "49.99". The customer's name is split across first_name and last_name. The email lives at the top level on some events and inside the customer object on others. The shipping address is a nested object with a dozen fields. The line items are an array where each item repeats the full product detail, and you usually want only the title, quantity, and price. None of this is hard individually. Together it is a function with twenty lines of optional chaining that breaks the first time Shopify adds a field or a customer checks out as a guest.

Instead of writing that function, you can demonstrate the result you want. You paste a representative order payload as the input and the clean record as the output. The engine compares the structures, infers the operations that connect them — field extraction, money-string-to-number coercion, name concatenation, array projection — and gives you a rule you can inspect and export.

Latentmachine runs this in the browser, so the order data never leaves your machine. The examples below show the input shape, the output you want, the inferred rule, and the exported code.

Order webhook to a flat database record

A trimmed orders/create payload looks roughly like this:

{
  "id": 820982911946154500,
  "order_number": 1001,
  "total_price": "49.99",
  "currency": "EUR",
  "financial_status": "paid",
  "customer": {
    "first_name": "Ana",
    "last_name": "Lopez",
    "email": "ana@example.com"
  }
}

For your database you want a flat, typed record:

{
  "orderNumber": 1001,
  "amount": 49.99,
  "currency": "EUR",
  "status": "paid",
  "customerName": "Ana Lopez",
  "customerEmail": "ana@example.com"
}

Show two example orders. The engine infers:

$.order_number → $.orderNumber
number($.total_price) → $.amount
$.currency → $.currency
$.financial_status → $.status
concat($.customer.first_name, " ", $.customer.last_name) → $.customerName
$.customer.email → $.customerEmail

Status: safe. The money string "49.99" is coerced to the number 49.99, the two customer name fields are concatenated with a space, the nested email is pulled up to a flat key, and financial_status is renamed to status. The large numeric id is dropped simply because it does not appear in the output you asked for.

The exported JavaScript:

function transform(input) {
  return {
    orderNumber: input?.order_number,
    amount: Number(input?.total_price),
    currency: input?.currency,
    status: input?.financial_status,
    customerName: `${input?.customer?.first_name} ${input?.customer?.last_name}`,
    customerEmail: input?.customer?.email,
  };
}

Open Latentmachine →

Reshaping the line items array

The part that makes Shopify payloads heavy is line_items. Each entry repeats far more than you need:

{
  "line_items": [
    {
      "id": 466157049,
      "title": "Solar Panel 300W",
      "quantity": 2,
      "price": "299.00",
      "sku": "SKU-300",
      "vendor": "Acme",
      "taxable": true,
      "requires_shipping": true
    },
    {
      "id": 466157050,
      "title": "Mounting Kit",
      "quantity": 1,
      "price": "45.00",
      "sku": "SKU-MNT",
      "vendor": "Acme",
      "taxable": true,
      "requires_shipping": true
    }
  ]
}

For a fulfillment system you want only three fields per item, with the price as a number:

{
  "items": [
    { "name": "Solar Panel 300W", "qty": 2, "unitPrice": 299 },
    { "name": "Mounting Kit", "qty": 1, "unitPrice": 45 }
  ]
}

The engine infers an array projection with renaming and coercion:

$.line_items project(title→name, quantity→qty, number(price)→unitPrice) → $.items

It detected that the output array keeps only title, quantity, and price from each item, renames them, coerces the price string to a number, and drops everything else — the id, sku, vendor, and the boolean flags. This is a structural operation on the array contents, inferred from the example rather than hand-coded.

Open Latentmachine →

Pulling out the shipping address

Fulfillment and shipping-label APIs want a flat address. Shopify nests it:

{
  "shipping_address": {
    "address1": "12 Main St",
    "address2": "",
    "city": "Berlin",
    "zip": "10115",
    "country_code": "DE",
    "phone": "+49 30 1234567"
  }
}

You need:

{
  "street": "12 Main St",
  "city": "Berlin",
  "postalCode": "10115",
  "country": "DE",
  "phone": "+49301234567"
}

The engine infers:

$.shipping_address.address1 → $.street
$.shipping_address.city → $.city
$.shipping_address.zip → $.postalCode
$.shipping_address.country_code → $.country
normalize-phone($.shipping_address.phone) → $.phone

Four nested fields are flattened and renamed, and the phone field gets a normalization because the output removes spaces while preserving the country code. The empty address2 is dropped because it does not appear in the output. If you later need address2 included only when it is non-empty, that is conditional business logic, a case the next section covers.

Open Latentmachine →

Where example-based inference stops, and why that is good

Some Shopify reshaping needs information or logic that is not present in the payload itself. Here the engine is deliberately honest about its limits rather than guessing.

Guest checkouts and missing fields. Guest orders may omit the customer object. If your rule extracts $.customer.email and a guest order arrives without it, the engine flags the missing field and names the operation that depends on it, instead of producing a record with a silent undefined email. You can add a guest-order example to teach it the fallback shape.

Unseen status values. If you map financial_status values through a lookup — "paid" to "complete", "pending" to "awaiting" — and an order arrives with "partially_refunded", the engine marks that field unresolved. It will not invent an output for a status it has never seen. Use a direct pass-through instead of a value map if you want unknown statuses to flow through untouched.

Computed totals and tax math. If you need amount_after_tax derived by subtracting a tax line, and that number is not directly present as a field in the payload, the engine can infer simple arithmetic between fields that are present, but it will not fabricate business rules. Transformations that depend on logic outside the input — currency conversion using today's rate, tax rules by region — are outside what examples can prove, and the engine says so rather than pretending.

The reason this matters for Shopify specifically: order webhooks fire constantly, on data you are not watching. A transformation that quietly drops the email on every guest checkout does not crash. It writes broken records for weeks. A tool that flags the missing field at inference time catches that before it ships.

Exporting for your stack

Once a rule is safe, export it where you need it:

Every export includes a header comment listing the rule status and operations, so a teammate reading the webhook handler can see what the mapping does without reverse-engineering it.

A practical workflow

1. Capture one real orders/create payload from a Shopify test order or the webhook logs.

2. Write out the clean record you want from it, with the correct types — numbers as numbers, not strings.

3. Paste both into Latentmachine as one input-output pair.

4. Add a second order with different values, ideally including a guest checkout or a multi-item order, so the engine can rule out coincidental matches and surface the missing-field cases.

5. Read the inferred rule. Check the money coercions and the line-item projection in particular.

6. Export the code into your webhook handler. The rule is deterministic, so every order of the same shape transforms the same way.

The work moves from maintaining a brittle mapping function to writing two honest examples and reading what the engine found. For a payload as large and as frequently changing as a Shopify order, that is where the time goes back into the day.

Open Latentmachine →