---
title: "Accept form and XML webhooks, and split a list into one Flow run per item"
description: "Workflow Webhooks parses JSON, form-encoded, multipart and XML bodies, and can fire one Shopify Flow run for every item in an array."
canonical: "https://docs.workflow-webhooks.app/body-formats-and-arrays"
---

# Body formats and splitting arrays

Not every system sends JSON, and not every request is about a single thing. Two settings cover both cases.

## Body formats

The format is picked from the request's `Content-Type` header. Whatever the format, your field mapping uses the same dot paths.

| Content type | Parsed as | Typical senders |
| --- | --- | --- |
| `application/json` | JSON | Most APIs, n8n, Make, Zapier |
| `application/x-www-form-urlencoded` | Form fields | Twilio, PayPal IPN, plain HTML forms |
| `multipart/form-data` | Form fields, file parts become the file name | Form builders, upload endpoints |
| `text/xml`, `application/xml`, `*+xml` | XML | Older ERP and carrier systems |

Two details worth knowing:

- A body that is **valid JSON is always read as JSON**, even when the sender labels it as something else. Many tools send JSON with a form content type, and this keeps them working.
- Form and XML bodies may contain fields you have not mapped. That is fine: the sender decides its own shape, so those bodies skip the strict check that applies to JSON you control.

```bash
curl -X POST https://your-app-url/webhook/ab12cd34 \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -H "X-Api-Key: your-token" \
  --data-urlencode "From=+15551234567" \
  --data-urlencode "Body=Where is my order?"

# Map fieldOne to: From
# Map fieldTwo to: Body
```

### XML attributes

An XML attribute is available under its name prefixed with `@_`, so `<order id="7">` is mapped as `order.@_id`, and `<order><name>Bob</name></order>` is `order.name`. Values arrive as text, which keeps long ids exact.

## Splitting arrays into runs

When one request carries a list - 20 orders from an ERP, a batch of stock updates - you usually want your workflow to run once per item, not once for the whole batch.

Under **Advanced Settings -> Split arrays into runs**, name the path to the array:

| Path | Use when |
| --- | --- |
| `items` | The array is a top-level field: `{ "items": [ ... ] }` |
| `data.orders` | It is nested: `{ "data": { "orders": [ ... ] } }` |
| `$` | The body itself is the array: `[ { ... }, { ... } ]` |
| *(empty)* | Off. One run per request, the default. |

Each element becomes its own run with **that element as the payload**, so mapping paths are relative to the item: map `sku`, not `items.0.sku`.

```json
{
  "items": [
    { "sku": "ABC-1", "qty": 2 },
    { "sku": "ABC-2", "qty": 1 },
    { "sku": "ABC-3", "qty": 7 }
  ]
}

// Split path: items
// Map fieldOne to: sku
// Map fieldTwo to: qty
// Response: { "runs": 3, "blocked": 0 }
```

> [!WARNING]
> **Each item counts toward your plan**
> Ten items in one request are ten invocations. If only part of a batch fits in what is left of your plan, those runs are sent and the rest are counted as blocked and reported in the response and on your dashboard.

### Rules and limits

- At most **100 items** per request. A larger batch is rejected so a runaway sender cannot flood your workflow.
- If **any** item is missing a mapped field or is too large for Flow, the whole request is rejected and nothing is sent, with the failing item's position in the error. That keeps a batch all-or-nothing rather than half-applied.
- Items that are plain values rather than objects arrive as `{ "value": ... }`.
- Splitting cannot be combined with a synchronous response: one caller cannot be answered by many runs. See [Synchronous response](https://docs.workflow-webhooks.app/synchronous-response.md).
- Replaying a past run from History re-sends that single item, not the whole batch.

### In History

Every item is its own entry, so you can see, retry and replay each one separately.
