How to Transform a Vendor CSV Webhook Payload into Clean JSON for an API
Accept a raw CSV body on a webhook, parse and deduplicate it with the CSV tools, reshape each row with a Transform node, and post the resulting JSON records to a downstream REST API.
What This Integration Does
Plenty of vendors and legacy systems do not speak clean JSON. They push you a comma-separated export over HTTP: a price list, a stock feed, a batch of new contacts. The receiving API on your side, however, expects well-formed JSON objects with the right field names and types. This Spojit workflow sits in the middle. It catches the CSV the moment it arrives, turns it into structured rows, strips out duplicate lines, renames and retypes each field into the shape your API wants, and delivers the records downstream. No manual download-and-reupload, no brittle spreadsheet macros.
The run model is push-based. An external system sends an HTTP POST containing the CSV text to the workflow's Webhook URL, and Spojit acknowledges immediately with a 202 and an executionId while the rest of the steps run asynchronously. Each run is self-contained: the CSV body flows from step to step as variables, gets reshaped in memory, and is posted to the target API. Nothing is stored between runs, so re-sending the same payload simply produces another run. To make replays safe, you control idempotency on the downstream side (described in Common Pitfalls) rather than relying on workflow state.
Prerequisites
- A workflow with a Webhook trigger. To verify the sender, attach a signing connection (Spojit, Shopify, GitHub, Slack, or Custom scheme) so only signed requests are accepted.
- The exact column headers the vendor sends in the CSV, and the exact field names, types, and endpoint your downstream API expects.
- An HTTP Requests connector available in your workspace (built in, no authentication needed) for posting to the API. If the target API needs a key or token, prepare those values so you can add them as request headers.
- Know whether the vendor sends the CSV as a raw text body or wrapped inside a JSON field. This determines how you read it in Step 2.
Step 1: Receive the CSV on a Webhook trigger
Add a Trigger node and set its type to Webhook. Spojit gives you a unique URL to hand to the vendor. When a request arrives, the trigger parses the body and exposes it as the run input. If the vendor posts well-formed JSON, you get the parsed object; if it posts a raw CSV text body, Spojit surfaces it as { raw: "..." }, so the CSV text is available at {{ input.raw }}. The trigger returns 202 with an executionId to the caller right away. Attach a signing connection on the trigger so unsigned or tampered requests are rejected before any processing happens.
Step 2: Convert the CSV text into JSON rows
Add a Connector node in Direct mode, choose the CSV Tools connector, and select the to-json tool. Map its csv input to the raw body from the trigger, for example {{ input.raw }} (or the specific field that holds the CSV string if it arrived wrapped in JSON). Leave header set to true so the first line becomes column keys, and set delimiter to , unless the vendor uses something else such as a semicolon or tab.
The tool returns items (an array of row objects keyed by the CSV column names) and count (the number of rows parsed). Name the output variable, for example parsedCsv, so later steps can reference {{ parsedCsv.items }} and {{ parsedCsv.count }}.
Step 3: Remove duplicate rows
Vendor exports often repeat lines, especially when a feed is regenerated mid-batch. Add another Connector node in Direct mode on the CSV Tools connector and select the dedupe tool. Because dedupe operates on CSV text, map its csv input back to the original body ({{ input.raw }}). Use the columns input to name the column or columns that define a unique record, for example a SKU or email column, so two rows are treated as duplicates only when those key columns match. Leave keepFirst at true to retain the first occurrence of each duplicate.
The tool returns the cleaned csv string plus a rows count of how many rows remained. Name the output, for example deduped. Then add one more CSV Tools to-json step against {{ deduped.csv }} to get a clean items array to reshape next. (If you prefer fewer steps, you can dedupe first and convert once; the order shown keeps each step single-purpose and easy to read in the execution logs.)
Step 4: Reshape each record with a Transform node
The parsed rows still carry the vendor's column names and string values. Add a Transform node to map them into the exact shape your API expects: rename keys, drop columns you do not need, and coerce values (for example turn a price string into a number). Work over the deduped rows array from Step 3. A reshaped record might look like this:
{
"sku": "{{ row.Item_Code }}",
"name": "{{ row.Description }}",
"unitPrice": "{{ row.Price }}",
"inStock": "{{ row.Qty }}"
}
Produce an array of these clean objects, ready to send as the request payload. Keep the field names identical to what the downstream API documents, including capitalization, since most REST APIs are case-sensitive.
Step 5: Loop and post each record to the API (or send a batch)
If your API accepts a single bulk payload, add one Connector node in Direct mode on the HTTP Requests connector with the http-post tool. Set the url to your endpoint, add any auth or content-type values as request headers, and map the body to the reshaped array from Step 4. If the API takes one record at a time, wrap the post in a Loop node in ForEach mode over the reshaped records, and inside the loop body call http-post with the single current record as the body:
POST https://api.example.com/v1/products
Content-Type: application/json
{
"sku": "{{ item.sku }}",
"name": "{{ item.name }}",
"unitPrice": "{{ item.unitPrice }}",
"inStock": "{{ item.inStock }}"
}
Capture each response so you can inspect status codes and bodies. If you need to validate values before sending (for example, drop rows with an empty SKU), add a Condition node inside the loop and only post when the check passes.
Step 6: Confirm or summarize the result
To tell an operator the import finished, add a Send Email node that reports how many records were posted, drawing on counts such as {{ parsedCsv.count }} and {{ deduped.rows }}. The recipient must be on your organization's allowlist under Settings > General > Email recipients. If the original caller is waiting on the HTTP response synchronously, you can instead add a Response node to return a small JSON summary such as the processed count, though most CSV imports are best left asynchronous given how long large files take.
Tips
- Use
dedupewith a specificcolumnslist rather than all columns, so rows that differ only in a noisy field (a timestamp or row number) still collapse correctly. - Send a small sample through Spojit's Miraxa, the intelligent layer across your automation, to scaffold the workflow: try a prompt like "Build a workflow with a Webhook trigger, a CSV Tools to-json step, a dedupe step, a Transform node, and an http-post to my API." Then fine-tune each node in the properties panel.
- For large files, prefer a single bulk
http-postif the API supports it. A per-row Loop multiplies request count and can hit downstream rate limits. - Keep each CSV step single-purpose (parse, then dedupe, then reshape). It makes the execution logs far easier to read when a run fails.
Common Pitfalls
- Wrong body shape. A raw CSV body lands at
{{ input.raw }}, not as a parsed object. Map thecsvinput accordingly, or you will pass an empty value intoto-json. - Delimiter surprises. European exports often use
;as the field delimiter. Set thedelimiterinput onto-jsonanddedupeto match, or every row collapses into one column. - Type drift. CSV values arrive as strings. If your API rejects
"12.50"where it wants a number, coerce the value in the Transform node before posting. - Webhook replays. Re-sending the same CSV produces another run. Make the downstream API idempotent (for example, upsert by SKU) so a replay does not create duplicate records, and enable the trigger's opt-in dedup via an event-id header where the vendor supports it.
Testing
Before you point a live vendor at the URL, validate on a tiny scope. Post a three-row CSV (with one deliberate duplicate) to the webhook using any HTTP client, then open the run in the execution logs and step through each node: confirm to-json produced the rows you expect, that dedupe removed the duplicate (check {{ deduped.rows }}), that the Transform output matches your API's field names exactly, and that the http-post returned a success status. Point the post at a test endpoint or sandbox first so you are not writing into production data while you tune the mapping. Once a small batch flows end to end, swap in the real endpoint and hand the URL to the vendor.