splitforms.com
All articles/ INTEGRATIONS9 MIN READPublished May 11, 2026

How to Send Contact Form Submissions to Trello 2026

Send contact form submissions directly to a Trello board in 2026 — webhook setup, card creation, custom field mapping, and a no-code path that works.

✶ Written by
splitforms.com / blog

Founder of splitforms — the form backend API for developers. Writes about form UX, anti-spam, and shipping web apps without backend code.

Why pipe form submissions into Trello at all

Email is fine for one form a week. The moment you have a sales pipeline, a hiring inbox, or a support queue, email collapses. You lose threads, you forget to follow up, two teammates accidentally reply to the same lead. Trello fixes the workflow without dragging a CRM into the picture: every submission becomes a card, every card moves through a board, nothing falls through the cracks.

The trick is wiring it up correctly. Most tutorials hand-wave the auth flow or push you onto Zapier's $30/month tier when a 30-line serverless function would do the job for free. This guide covers both: the direct API path you should use if you can write a little code, and the no-code Zapier path that's genuinely fine for a small team.

You'll need a splitforms account (free, 1,000 submissions/month — sign up at splitforms.com/login), a Trello account, and ten minutes. If you already use a webhook handler for other integrations, the patterns in our send form data to webhook guide carry over directly.

Two paths, picked by skill level

Pick one and skip the other:

PathSetup timeLatencyMonthly costBest for
Direct (webhook → Trello API)15 min200-600 ms$0Anyone who can deploy a Cloudflare Worker, Vercel function, or Lambda
Zapier (webhook → Zapier → Trello)5 min1-15 sec (paid) / 1-3 min (free)$0-$30Non-developers, ops teams, anyone allergic to deploys

If you're reading this on a dev blog, you almost certainly want path 1. Path 2 is here for your operations colleague who doesn't want to touch a terminal.

Trello REST API basics (the 3 things you need to know)

Trello's API is REST + key/token auth, no OAuth dance required for personal automation. Three concepts to grasp:

1. Auth: API key + token

Grab both from trello.com/app-key. The key identifies your "app" (yourself, here). The token is the per-user authorization. You append them as query params on every request:

https://api.trello.com/1/cards?key=YOUR_KEY&token=YOUR_TOKEN

2. IDs: board, list, card

Everything in Trello has a 24-character hex ID. To create a card you need the list ID it goes into. Get all lists on a board:

curl "https://api.trello.com/1/boards/BOARD_ID/lists?key=KEY&token=TOKEN"

# Returns:
# [
#   { "id": "65f1aa7c8d9e0f1234567890", "name": "Inbox" },
#   { "id": "65f1aa7c8d9e0f1234567891", "name": "Qualified" },
#   { "id": "65f1aa7c8d9e0f1234567892", "name": "High Priority" }
# ]

3. Create card endpoint

One call creates a card with a title, description, labels, due date, and optional list position. That's the entire surface area you need for this integration:

POST https://api.trello.com/1/cards
  ?idList=LIST_ID
  &name=Card+title
  &desc=Markdown+description
  &key=KEY
  &token=TOKEN

That's it. The rest is mapping form fields into those four parameters.

Path 1: splitforms webhook → Trello API directly

The fastest, cheapest, most reliable path. We'll set up a tiny serverless function (Cloudflare Worker, Vercel Edge Function, or AWS Lambda — pick your poison) that receives the splitforms webhook and turns it into a Trello card.

Step 1: Configure splitforms to fire a webhook

  1. In your splitforms dashboard, open your form's settings.
  2. Click WebhooksAdd webhook.
  3. Paste the URL of the serverless function you're about to deploy (e.g. https://your-worker.your-domain.workers.dev/trello).
  4. Copy the auto-generated signing secret. You'll need it for HMAC verification.
  5. Save. splitforms now POSTs the JSON payload to your function on every submission.

Step 2: The webhook payload you'll receive

Splitforms posts a clean JSON envelope. Here's what a typical contact form submission looks like:

{
  "id": "sub_01HXYZ123ABC",
  "form": { "id": "frm_01HV", "name": "Contact" },
  "submittedAt": "2026-05-11T14:22:01.443Z",
  "fields": {
    "name":    "Jordan Lee",
    "email":   "jordan@example.com",
    "company": "Acme Robotics",
    "budget":  "10000",
    "message": "Need help integrating our lead form with Trello."
  },
  "meta": {
    "ip": "203.0.113.42",
    "userAgent": "Mozilla/5.0 ..."
  }
}

Step 3: The handler (Cloudflare Workers / Vercel Edge)

This single file does the job. Drop it into a Cloudflare Worker or a Next.js route handler:

// /api/trello-webhook.ts  (Next.js route handler)
import crypto from "node:crypto";

const TRELLO_KEY    = process.env.TRELLO_KEY!;
const TRELLO_TOKEN  = process.env.TRELLO_TOKEN!;
const LIST_INBOX    = process.env.TRELLO_LIST_INBOX!;
const LIST_URGENT   = process.env.TRELLO_LIST_URGENT!;
const SPLITFORMS_SECRET = process.env.SPLITFORMS_SECRET!;

export async function POST(req: Request) {
  const raw = await req.text();
  const sig = req.headers.get("x-splitforms-signature") ?? "";

  // 1. Verify HMAC signature
  const expected = crypto
    .createHmac("sha256", SPLITFORMS_SECRET)
    .update(raw)
    .digest("hex");
  if (!crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) {
    return new Response("bad signature", { status: 401 });
  }

  const { fields, submittedAt } = JSON.parse(raw);

  // 2. Route urgent leads to the High Priority list
  const budget = Number(fields.budget ?? 0);
  const idList = budget >= 5000 ? LIST_URGENT : LIST_INBOX;

  // 3. Map form fields to card shape
  const name = `${fields.name} — ${fields.company ?? "no company"}`;
  const desc = [
    `**Email:** ${fields.email}`,
    `**Company:** ${fields.company ?? "—"}`,
    `**Budget:** $${budget.toLocaleString()}`,
    `**Submitted:** ${submittedAt}`,
    "",
    "---",
    "",
    fields.message ?? "",
  ].join("\n");

  // 4. Create the Trello card
  const params = new URLSearchParams({
    idList,
    name,
    desc,
    key: TRELLO_KEY,
    token: TRELLO_TOKEN,
  });
  const res = await fetch(`https://api.trello.com/1/cards?${params}`, {
    method: "POST",
  });

  if (!res.ok) {
    console.error("trello error", res.status, await res.text());
    return new Response("trello failed", { status: 502 });
  }

  return new Response("ok", { status: 200 });
}

That's the entire integration. Deploy, paste the URL into the splitforms webhook config, submit your form, watch the card appear. The whole thing is < 60 lines.

Step 4: Test with curl before going live

Before pointing real traffic at this, hit the Trello API directly to confirm your key, token, and list ID work:

curl -X POST "https://api.trello.com/1/cards" \
  -d "idList=YOUR_LIST_ID" \
  -d "name=Test card from curl" \
  -d "desc=If you see this in Trello, auth works." \
  -d "key=YOUR_KEY" \
  -d "token=YOUR_TOKEN"

If you get a 200 and a card appears on the board, your credentials are good. If you get 401, the token is wrong or expired. If you get 400 with "invalid id", the list ID is wrong.

Mapping form fields → card title, description, labels

The mapping decisions matter more than the code. Here's a sensible default for a contact / lead capture form:

Form fieldGoes intoWhy
name + companyCard titleScannable in the list view
emailCard description (top)Click-to-copy from card detail
messageCard description (body)Long text needs the markdown area
budget / urgencyLabel color + list routingVisual signal in the board overview
source / utmLabelFilter the board by acquisition channel
submittedAtDue date or card metadataSort by recency, follow-up SLAs
Uploaded filesCard attachmentResume, brief, screenshot stays with the lead

To add labels, first list label IDs on your board (GET /1/boards/{id}/labels), pick the relevant ones, then add idLabels as a comma-separated list in your card POST:

const params = new URLSearchParams({
  idList,
  name,
  desc,
  idLabels: ["lbl_marketing", "lbl_high_budget"].join(","),
  due: dueIsoString,           // optional: 7 days from now
  pos: "top",                  // newest leads at top of the list
  key: TRELLO_KEY,
  token: TRELLO_TOKEN,
});

If you're on a paid Trello plan with Custom Fields enabled, you can also call PUT /1/cards/{cardId}/customField/{fieldId}/item after creating the card to drop budget, source, or any other structured value into a typed field. For most teams, putting it in the description is enough.

Bonus: route urgent leads to a "High Priority" list

The pattern that makes Trello actually useful is conditional routing. Don't dump every submission into one inbox list — pre-sort them so the high-value ones get attention first.

Two columns in your code map to two list IDs:

function pickList(fields: Record<string, string>): string {
  const budget = Number(fields.budget ?? 0);
  const urgency = (fields.urgency ?? "").toLowerCase();
  const role = (fields.role ?? "").toLowerCase();

  // High-value enterprise leads
  if (budget >= 25000) return process.env.TRELLO_LIST_URGENT!;

  // Self-selected urgent
  if (urgency === "asap" || urgency === "high") {
    return process.env.TRELLO_LIST_URGENT!;
  }

  // Decision-maker title
  if (/ceo|cto|founder|vp|head of/.test(role)) {
    return process.env.TRELLO_LIST_URGENT!;
  }

  return process.env.TRELLO_LIST_INBOX!;
}

Keep the routing rules close to the code, not in a third-party tool. You can grep them, test them, and change them without clicking through a dashboard. Add a console.log with the decision and the matched rule — your future self will thank you when a lead lands in the wrong list and you need to understand why.

For multi-team triage, the same pattern extends: route by department (sales, support, partnerships), each to its own board or list. Splitforms can also forward submissions to multiple emails in parallel with the webhook, so each team gets the email notification as well as the Trello card.

Error handling and retries

Things will fail. Plan for it.

  • Return the right status code. If Trello returns 5xx or your function times out, splitforms retries the webhook with exponential backoff (5 attempts over ~13 minutes). Return 200 only when the card was actually created. Return 5xx if Trello failed transiently. Return 4xx (and log) if the payload was malformed — those won't be retried.
  • Idempotency. If splitforms retries because Trello was slow but the card was actually created, you'll get a duplicate. Defend against it: use the splitforms submission id as a key in a small KV store (Cloudflare KV, Upstash Redis, even a DynamoDB row). On retry, check if you've already processed that ID and return 200 without re-creating.
  • Trello rate limits. Trello allows 300 requests per 10 seconds per API key and 100 per 10 seconds per token. You won't hit it from a normal contact form, but if you batch-import historical data, throttle.
  • Logging. Log the response body when Trello returns non-2xx. Their error messages are usually clear ("invalid id", "unauthorized", "rate limit exceeded"). Send the log to wherever you read logs — Cloudflare Logpush, Vercel logs, Logtail.
  • Dead-letter strategy. If all retries fail, the splitforms dashboard shows the failed delivery with the full request body. You can replay it manually after fixing the bug. The submission email still arrived in your inbox regardless.

Path 2: splitforms → Zapier → Trello (no-code)

If writing serverless code is not happening, Zapier is fine. The trade-offs are real but acceptable for low-volume forms: 1-3 minute latency on the free plan, 1-15 second latency on paid plans, and you pay per task at scale. Full setup walked through in connect splitforms to Zapier if you want every screenshot. The short version:

  1. In Zapier, click Create Zap.
  2. Trigger: search "Webhooks by Zapier", pick Catch Hook. Zapier gives you a URL like https://hooks.zapier.com/hooks/catch/123/abc/.
  3. In the splitforms dashboard, add that URL as a webhook on your form.
  4. Submit a test entry on your form so Zapier sees a sample payload — it'll auto-discover all your form's field names.
  5. Action: search "Trello", pick Create Card.
  6. Connect your Trello account (OAuth flow, one click).
  7. Pick the board and the list. Map fields: card name = {{fields__name}} — {{fields__company}}, description = the message, etc.
  8. Turn on the Zap. Done.

For urgent-list routing in Zapier, add a Filter step or use a Paths action (paid plan) to branch based on field values. Filter-based routing eats one task per branch evaluated, which is why direct webhooks are cheaper at volume.

When Zapier is the right choice

If you submit fewer than ~50 forms a month, Zapier's free tier covers it. If a non-technical teammate needs to edit the integration, Zapier's visual editor beats code. If you're wiring Trello to four other tools (Slack, Notion, Mailchimp) in the same flow, the multi-step Zap is hard to beat for setup time. Pick the path that matches your team, not the path that wins benchmarks.

Testing the whole pipeline before going live

Don't flip the form on production and hope. Verify each link in the chain:

  1. Trello API auth: the curl test from earlier. Card appears = key, token, and list ID all good.
  2. Webhook handler locally: use ngrok http 3000 to expose your local function, point splitforms at the ngrok URL, submit a test form. Watch your terminal logs.
  3. HMAC verification: deliberately tamper with the body before validating, confirm you return 401. Then revert and confirm you return 200.
  4. End-to-end with the deployed handler: point splitforms at the production URL, submit through your real form on your real domain, time the card appearing. Should be under a second.
  5. Urgent routing: submit one entry with low budget, one with high budget. Confirm they land in different lists.

If a submission silently doesn't reach Trello, check splitforms' webhook delivery log first — it shows every attempt with response status and body. Nine times out of ten the answer is right there: 401 means wrong token, 400 means bad list ID, 5xx means your handler is throwing.

Next steps and related guides

FAQ

Do I need a paid Trello account to receive form submissions?

No. Trello's free plan covers unlimited cards on up to 10 boards per Workspace and full REST API access. Splitforms' webhook will POST to the Trello API with your API key and token regardless of plan. You only need a paid Trello plan (Standard, Premium, Enterprise) for advanced features like custom fields on every card, board admin restrictions, or unlimited Workspace boards. For a basic contact-form-to-card pipeline, the free tier is more than enough.

Where do I get my Trello API key and token?

Sign in to Trello, then go to trello.com/app-key to grab your API key. On that same page click the Token link to generate a personal token — Trello will ask you to authorize read/write access. Treat the token like a password: it grants full account access. Store it in an environment variable (TRELLO_TOKEN) on whatever serverless function receives the splitforms webhook. Never paste it into client-side JS or commit it to git.

How do I find the list ID I want cards created in?

Open the Trello board in your browser. Append `.json` to the URL — e.g. `trello.com/b/AbCd1234/leads.json` becomes `trello.com/b/AbCd1234/leads.json`. The response is JSON; search for the list name and grab its `id` (a 24-char hex string). Or use the API directly: `GET https://api.trello.com/1/boards/{boardId}/lists?key=KEY&token=TOKEN` returns all lists with IDs. Copy the ID for your inbox/leads list and save it in code.

Can I attach the submitter's file uploads to the Trello card?

Yes. Splitforms exposes uploaded files as signed URLs in the webhook JSON payload (`fields.resume`, `fields.attachment`, etc.). In your handler, after the card is created, POST each URL to `https://api.trello.com/1/cards/{cardId}/attachments?url=…`. Trello will pull the file from the URL and attach it to the card. Free Trello supports attachments up to 10 MB; Standard goes to 250 MB per file.

What happens if Trello's API is down when a form is submitted?

Splitforms retries failed webhooks with exponential backoff (1, 5, 25, 125, 625 seconds — up to 5 attempts over ~13 minutes). The submission email still lands in your inbox regardless of webhook status, so you never lose the lead. If your handler returns 5xx or times out, splitforms retries; if you return 4xx, it doesn't (we assume the request is malformed). You can also see all webhook attempts in the splitforms dashboard with full response bodies.

Is this faster than Zapier?

Yes, by a lot. A direct webhook → Trello API round trip lands the card in 200-600 ms. Zapier polls or queues with a typical 1-3 minute delay on free plans and 1-15 seconds on paid plans. For a contact form, the visible difference is whether the card shows up the moment you click into the board, or after you've already brewed coffee. If speed matters (sales leads, on-call alerts), use the direct webhook path.

Can I create cards in different lists based on form data?

Yes — that's the urgent-routing pattern. In your webhook handler, branch on a form field (`fields.urgency === 'high'`, `fields.budget > 10000`, etc.) and pick the right list ID before POSTing to `/1/cards`. You can also assign different labels, due dates, or members per branch. Keep the routing logic in code rather than per-field-mapping; it's easier to test and you can log decisions.

Do I need to verify the webhook signature?

Yes, in production. Splitforms signs every webhook with HMAC-SHA256 using your shared secret and sends the hash in the `X-Splitforms-Signature` header. Compute the same HMAC over the raw request body and compare with `crypto.timingSafeEqual`. Without verification, anyone who guesses your endpoint URL could spam your Trello board with fake cards. The verification snippet is in /docs.

About the author
✻ ✻ ✻

Get your free contact form API key in 60 seconds.

1,000 free form submissions per month. No credit card. No SDK, no PHP, no plugin. Drop one POST endpoint in your form and submissions land in your inbox.

Generate access key →Read the docs
v0.1 · founders pricing locked in · early access open