Receive data
Webhook delivery
Deliver CSVJetty imports to an HTTPS webhook, verify HMAC signatures, reject individual rows, and safely handle retries with idempotency keys.
Webhook delivery sends imported records from CSVJetty to an HTTPS endpoint you control. Use it when you prefer server-to-server delivery instead of handling rows in the browser.
Configure a webhook
Select Webhook in the importer's delivery settings and enter your HTTPS endpoint. The React component does not need an onData callback. Compare this path with browser callbacks in Delivery modes.
ImportContacts.jsx
import CSVJetty from "csvjetty";
export default function ImportContacts() { return ( <CSVJetty importerKey="YOUR_IMPORTER_KEY"> Import contacts </CSVJetty> );}Webhook requests
CSVJetty sends records in batches of up to 1,000 rows. Large individual records can result in smaller batches. Each request includes:
Idempotency-Key: the import ID and batch number.CSVJetty-Signature: a timestamp and HMAC-SHA256 signature.
Webhook payloads include the normal import payload plus zero-basedbatch andtotalBatches values. See the complete Import payload reference.
Verify signatures
Compute HMAC-SHA256 over the timestamp, a period, and the exact raw request body. Compare the digest in constant time and reject timestamps more than five minutes old.
pages/api/csvjetty-webhook.js
import crypto from "node:crypto";
export const config = { api: { bodyParser: false, },};
async function readRawBody(req) { const chunks = []; for await (const chunk of req) chunks.push(Buffer.from(chunk)); return Buffer.concat(chunks);}
function validSignature(rawBody, header, secret) { const parts = Object.fromEntries( String(header || "") .split(",") .map((part) => part.trim().split("=")) );
const timestamp = Number(parts.t); const signature = parts.v1;
if (!Number.isFinite(timestamp) || !signature) return false; if (Math.abs(Date.now() / 1000 - timestamp) > 300) return false;
const expected = crypto .createHmac("sha256", secret) .update(String(timestamp)) .update(".") .update(rawBody) .digest();
let received; try { received = Buffer.from(signature, "hex"); } catch { return false; }
return ( expected.length === received.length && crypto.timingSafeEqual(expected, received) );}
export default async function handler(req, res) { if (req.method !== "POST") return res.status(405).end();
const rawBody = await readRawBody(req); const signature = req.headers["csvjetty-signature"];
if (!validSignature(rawBody, signature, process.env.CSVJETTY_WEBHOOK_SECRET)) { return res.status(401).end(); }
const payload = JSON.parse(rawBody.toString("utf8")); const idempotencyKey = req.headers["idempotency-key"];
// Deduplicate idempotencyKey, validate payload.rows, then persist the records. await saveImportBatch({ payload, idempotencyKey });
return res.status(200).json({ errors: [] });}Verify the raw body
Return a response
Return any HTTP 2xx response with an empty body, or return an errors array.
JSON
{ "errors": []}To reject individual records, return objects containing the original CSV row number and an error message, using the same format as callback delivery.
Retries and idempotency
Deduplicate every request using Idempotency-Key. Saved batches can be retried from the CSVJetty dashboard within 24 hours of the first batch, and batches that already succeeded are skipped.