Tips > Ops & Security

Sanitize All User Input From Webhooks

Webhook payloads are untrusted user input.

Treat every webhook payload as untrusted input. Before using it in a database query, API call, or shell command, validate and sanitize it in a Code node, then pass the values to parameterized queries so the database escapes them. This blocks SQL and NoSQL injection, command injection, and SSRF instead of interpolating raw user input into a query string.

Why must you sanitize webhook input?

Webhook payloads are untrusted user input. Using them directly in database queries, API calls, or shell commands opens your workflows to injection attacks including SQL injection, NoSQL injection, command injection, and server-side request forgery (SSRF).

Real-world example: A webhook receives a contact form submission and inserts it into a PostgreSQL database. An attacker submits '; DROP TABLE contacts; -- as the name field.

How do you sanitize and validate webhook input?

Never interpolate raw input into a query string. This is vulnerable to injection:

// BAD: Direct string interpolation into SQL
// Mode: Run Once for Each Item
const name = $input.item.json.name;
const email = $input.item.json.email;

// VULNERABLE -- never do this
const query = `INSERT INTO contacts (name, email) VALUES ('${name}', '${email}')`;

Instead, sanitize and validate the input first, then hand the clean values to parameterized queries:

// GOOD: Sanitize and validate input, use parameterized queries
// Mode: Run Once for Each Item

function sanitizeString(input, maxLength = 255) {
  if (typeof input !== 'string') return '';
  return input
    .trim()
    .slice(0, maxLength)
    .replace(/[<>'";&|`$\\]/g, ''); // Remove dangerous characters
}

function isValidEmail(email) {
  return /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(email);
}

const name = sanitizeString($input.item.json.name, 100);
const email = $input.item.json.email?.trim().toLowerCase() ?? '';

if (!name || !isValidEmail(email)) {
  return [{ json: { error: 'Invalid input', rejected: true } }];
}

// Pass sanitized values to a Postgres node using parameterized queries
return [{
  json: {
    sanitizedName: name,
    sanitizedEmail: email,
    receivedAt: new Date().toISOString(),
  }
}];

Then in the PostgreSQL node, use n8n's built-in parameterized query support:

-- PostgreSQL node query (parameters are auto-escaped by the node)
INSERT INTO contacts (name, email, received_at)
VALUES ($1, $2, $3)

Map $1 to {{ $json.sanitizedName }}, $2 to {{ $json.sanitizedEmail }}, and $3 to {{ $json.receivedAt }}.

How do you prevent SSRF from user-supplied URLs?

Warning: SSRF Attacks. If your webhook accepts a URL parameter and your workflow fetches that URL, an attacker can make your server request internal resources (e.g., http://169.254.169.254/latest/meta-data/ on AWS to steal instance credentials). Always validate and allowlist URL domains before fetching.

Related: Use Docker Compose with Health Checks for n8n and PostgreSQL · Use Path Parameters in Webhook URLs for Dynamic Routing

Showcase builds

19 complete workflows from my own projects, each with its n8n workflow JSON to import. Showcase entries link the file at the end of the article.

See the showcase builds

Keep reading

190 entries grouped by topic, from first workflow to queue mode. Free, no signup.

Browse the encyclopedia

Need it built?

I design, build and run n8n systems for clients. Every engagement starts with a $1,500 diagnostic audit, credited toward the build.

Book an introductory call