Skip to content

George-Kariuki/csv-to-json

Repository files navigation

CSV/Excel to JSON Webhook Trigger

A minimal mobile embeddable web tool. The user uploads a CSV or Excel file; it is parsed to JSON entirely in the browser and POSTed directly to a webhook URL supplied via a query parameter. No file ever touches a server in the default flow.

Quick start

npm install
npm run dev

Open:

http://localhost:3000/?webhook=https://webhook.site/<your-id>

Upload a .csv, .xls, or .xlsx file and watch the JSON arrive at your webhook.

URL parameters

Param Required Description
webhook Yes URL-encoded http(s) endpoint that receives the POST.
token No Arbitrary string echoed back in the payload for lightweight auth.
source No Arbitrary label echoed back in the payload (e.g. mobile, kiosk).

Example:

https://yourtool.com/?webhook=https%3A%2F%2Fhooks.example.com%2Fflow&token=abc123&source=mobile

Remember to URL-encode the webhook value: encodeURIComponent("https://hooks.example.com/flow").

Webhook payload

The tool sends a single JSON POST with Content-Type: application/json:

{
  "filename": "contacts.csv",
  "rowCount": 2,
  "source": "mobile",
  "token": "abc123",
  "receivedAt": "2026-06-10T06:40:00.000Z",
  "data": [
    { "name": "Ada", "email": "ada@example.com" },
    { "name": "Linus", "email": "linus@example.com" }
  ]
}

data is an array of row objects keyed by the file's header row. CSV values are type-coerced (numbers/booleans) where possible; Excel uses the first sheet.

Embedding

iframe (mobile web page)

<iframe
  src="https://yourtool.com/?webhook=https%3A%2F%2Fhooks.example.com%2Fflow&token=abc123"
  style="width: 100%; height: 360px; border: none;"
  allow="clipboard-write"
></iframe>

React Native WebView

import { WebView } from "react-native-webview";

const webhook = encodeURIComponent("https://hooks.example.com/flow");

<WebView
  source={{ uri: `https://yourtool.com/?webhook=${webhook}&source=app` }}
  originWhitelist={["*"]}
/>;

Listening for state from the parent (optional)

The embedded tool emits postMessage updates to its parent on every state change:

window.addEventListener("message", (e) => {
  if (e.data?.type === "csv-to-json-webhook") {
    // { phase, fileName, rowCount, message }
    console.log(e.data.phase);
  }
});

CORS — important

The browser POSTs directly to your webhook from a different origin, so the webhook must return:

Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: Content-Type

Compatibility:

  • Your own server / n8n webhook node — works (configure CORS).
  • Zapier / Make (Integromat) — browser POSTs are typically blocked by CORS; use the proxy below.

Optional proxy (CORS bypass)

If the target webhook can't send CORS headers, route through the included server-side proxy at app/api/convert/route.ts. It forwards the payload from the server, where CORS does not apply.

To enable it, change the fetch target in app/embed/EmbedTool.tsx from webhook to:

/api/convert?webhook=${encodeURIComponent(webhook)}

Before exposing publicly, lock the proxy down: add ALLOWED_WEBHOOK_HOSTS and/or a shared-secret check so it can't be abused as an open relay (SSRF).

Security notes

  • The webhook URL is visible in the address bar and browser history. Anyone with the link can POST to it. Use the token param (validated on your webhook) or HMAC signing for sensitive flows.
  • Parsing is capped at 25 MB to protect the browser.

Deploy

Deploy to Vercel (or any Node host):

npm run build
npm start

next.config.ts sets Content-Security-Policy: frame-ancestors * so the page can be embedded anywhere. Restrict that to your domains in production.

About

A minimal mobile embeddable web tool. The user uploads a CSV or Excel file; it is parsed to JSON entirely in the browser and POSTed directly to a webhook URL supplied via a query parameter. No file ever touches a server in the default flow.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors