All resources
Automations & integrations

How to Send Form Submissions to Discord

Post every form submission into a Discord channel as a formatted embed — and why calling a Discord webhook from browser JavaScript leaks it to everyone who visits the page.

The ShipMyForm team

· 7 min read

Short answer: a Discord webhook URL is a credential, not an endpoint, so your form must not post to it directly — put a server in between, have that server build Discord's own message JSON, and send it from there. With ShipMyForm that is the Discord connector (paste the webhook URL once) or the free webhook connector pointed at an automation that reshapes the payload.

Full disclosure: ShipMyForm is our product. The webhook-exposure problem and the embed limits in this guide are true of every backend, and the free route works without paying us anything.

The mistake almost every tutorial makes

Search for this and you will find the same snippet everywhere: a fetch() call from the browser straight to https://discord.com/api/webhooks/.... It appears to work. Submit the form, a message lands in the channel, done.

It is also a channel you have handed over to the internet. That webhook URL is sitting in your page source, and a Discord webhook URL is a bearer credential — there is no second factor, no origin check, no signature. Anyone who opens dev tools, reads your bundle, or finds the page in an archive can post anything they like into that channel, as whatever username and avatar they choose, for as long as the webhook exists. Your only remedy is deleting the webhook and creating a new one, which breaks every copy of the form still in the wild.

How to tell if you already have this problem:

Open the page with your form, view source or search your built JS bundle for discord.com/api/webhooks. If it is there, the webhook is public. Delete that webhook in Discord — deleting it is the revocation — and create a fresh one for the server-side route below.

There is a second, quieter reason the direct route fails: shape. A Discord webhook expects Discord's own message JSON — a content string, or an embeds array — not your form's fields. Post name=Jane&message=hello at it and Discord has nothing it recognises. So even setting the credential aside, something has to translate between your form and Discord's message format.

Both problems have the same fix: a server in the middle.

text
your form ──POST──▶ ShipMyForm ──▶ spam filter ──▶ stored ──▶ Discord
                  (holds the webhook URL, builds the embed)

The two routes at a glance

Managed Discord connectorWebhook + automation
SetupPaste one Discord webhook URLWire an automation to reshape the payload
PlanPaid (Starter and up)Free plan works
Message formattingEmbed, built for youYou build the Discord message yourself
Webhook URLStored server-side, never in your pageStored in your automation
MaintenanceNoneYou own the automation

Either way the form itself stays plain HTML with no JavaScript, and the webhook URL never reaches a browser.

Option A: the managed Discord connector

Step 1: point your form at ShipMyForm

html
<form action="https://shipmyform.com/f/YOUR_FORM_ID" method="POST">
  <input name="name" required />
  <input name="email" type="email" required />
  <textarea name="message" required></textarea>
  <button type="submit">Send</button>
</form>

No fetch, no API key in the page, nothing secret in the markup.

Step 2: create a channel webhook in Discord

A Discord webhook posts into one specific channel. To create one, you need Manage Webhooks permission on the server:

  1. Open the target channel's settings (the cog beside the channel name).
  2. Go to Integrations → Webhooks → New Webhook.
  3. Give it a name — this is the default display name, though ShipMyForm overrides it per message — and confirm the channel.
  4. Click Copy Webhook URL. It looks like https://discord.com/api/webhooks/000000/xxxxxxxx.

Treat what you just copied like a password. Do not commit it, do not paste it in a public channel, and do not put it in client-side code.

Step 3: add the Discord connector

In your dashboard, open the form, go to Connectors, and add Discord. Paste the URL and save. The connector only accepts a discord.com or discordapp.com webhook host, so a rejected paste almost always means you copied an invite link or a channel link rather than the webhook URL.

Step 4: send a test

Hit Send test in the connector, or just submit the form. An embed appears in the channel with:

  • a title — 📨 New submission: <form name>,
  • a field per submitted value, short values laid out side by side, and
  • a footer and timestamp for when the submission arrived.

Because spam is filtered upstream, only genuine submissions produce a post.

Posting into a thread or forum channel

Discord accepts a thread_id query parameter on a webhook URL, which posts into that thread instead of the parent channel. Append it to the URL you paste:

text
https://discord.com/api/webhooks/000000/xxxxxxxx?thread_id=123456789

One webhook means one destination. To split submissions — applications into #applications, feedback into #feedback — add a separate Discord connector per destination, each with its own URL.

The embed limits that truncate your messages

This is the part that surprises people a week in, so it is worth knowing before you rely on the channel post as your record. Discord's documented ceilings for an embed are:

LimitValue
Fields per embed25
Field name256 characters
Field value1,024 characters
Whole embed, all parts summed6,000 characters

A form with thirty fields, or a message textarea where somebody wrote four paragraphs, does not fit. ShipMyForm truncates to stay inside those limits rather than letting Discord reject the whole post — a trimmed notification is better than no notification.

Read the channel, open the dashboard:

Treat the Discord post as a notification, not the record. The complete, untruncated submission is always in your dashboard and in CSV exports. This is the right split anyway: chat is for knowing something arrived within seconds, and a long enquiry was never going to be comfortable to read in a channel.

Two smaller behaviours worth knowing if you build the DIY route:

  • Discord replies 204 No Content on success — an empty body is the success case, not a failure. Append ?wait=true if you want the created message object back instead, which is how you get a message ID to edit or reply to later.
  • Webhooks are rate limited. Send too fast and Discord replies 429 with a retry_after value telling you how long to wait. A contact form will never touch this; a form behind a traffic spike, or one connector fanning out to several channels, can. ShipMyForm retries failed deliveries, so a 429 is recovered rather than dropped.

Option B: free, via the webhook connector and an automation

On the free plan, or when you want the message to look exactly how you want it, skip the managed connector. ShipMyForm's free Webhook connector sends its own JSON — the submission with your fields under data — so you need something in between to turn that into Discord's message format:

text
ShipMyForm ──webhook (raw JSON)──▶ n8n / Zapier / Make ──▶ Discord
                                   (reshapes the payload)

Step 1: point the webhook connector at your automation

Open Connectors, add the Webhook connector (free on every plan), and paste your automation's inbound URL — an n8n Webhook node, a Zapier Catch Hook, or a Make custom webhook.

Step 2: reshape the payload and post to Discord

Read the incoming fields from data and build a Discord message. The minimum Discord accepts is a single string:

json
{
  "content": "📨 New submission from [email protected]: Loved the guide, can we talk?"
}

For something closer to the managed connector's output, send an embed:

json
{
  "username": "Contact form",
  "embeds": [
    {
      "title": "📨 New submission",
      "color": 6514417,
      "fields": [
        { "name": "name", "value": "Jane Doe", "inline": true },
        { "name": "email", "value": "[email protected]", "inline": true },
        { "name": "message", "value": "Loved the guide, can we talk?" }
      ]
    }
  ]
}

color is a decimal integer, not a hex string — a common first-attempt error. Convert #6366f1 to 6514417.

Verify the request is really from ShipMyForm:

Every webhook delivery carries an X-ShipMyForm-Signature header: sha256= followed by an HMAC-SHA256 of the raw body, keyed with the connector’s signing secret. Your automation’s inbound URL is open to the internet, so recompute that hash and reject anything that does not match — otherwise you have rebuilt the original problem one layer further back, with a URL anyone can use to post into your channel.

The full n8n walkthrough covers this pattern node by node, including the signature check, and the same shape works in Zapier and Make.

Which should you use?

  • Want it working in a minute? The managed connector. Paste the URL, get a formatted embed, nothing to maintain.
  • On the free plan, or want to design the message? The webhook connector into an automation. Free and fully yours, but you own the reshaping and the signature check.

Both keep the webhook URL off the client, which is the part that actually matters.

Next steps

Frequently asked questions

Can I post an HTML form straight to a Discord webhook?
Not safely, and usually not legibly. A Discord webhook expects Discord's own message JSON — a content string or an embeds array — so a raw form POST arrives in the wrong shape and produces nothing readable. The bigger problem is that calling the webhook from browser JavaScript puts the URL in your page source, and a Discord webhook URL is a bearer credential: anyone who reads it can post anything into that channel, forever, until you delete the webhook. Send the submission to a server you control and have that server call Discord.
Is the Discord connector available on the free plan?
The managed Discord connector is on ShipMyForm's paid plans (Starter and up). It posts each non-spam submission to your channel as a formatted embed with no code. On the free plan you can still reach Discord by pointing the free webhook connector at an automation (n8n, Zapier, or Make) that reshapes the payload into Discord's message format — that route is covered in this guide.
Why is a long message cut off in the Discord notification?
Discord caps each embed field value at 1,024 characters, caps an embed at 25 fields, and caps the whole embed at 6,000 characters. A long textarea answer is truncated to fit those limits, so the Discord post is a notification rather than the record. The full, untruncated submission is always stored in your dashboard and in CSV exports — read the channel post to know something arrived, then open the submission to read all of it.
Can I post submissions into a Discord thread or forum channel?
Yes, by appending ?thread_id=<id> to the webhook URL, which tells Discord to post into that thread instead of the parent channel. One webhook maps to one destination, so to split submissions across several channels or threads, add a separate Discord connector for each one with its own URL.
Will spam submissions post to my Discord channel?
No. ShipMyForm filters spam before anything reaches a connector, so only genuine submissions are delivered to Discord. That matters more on Discord than on email: a public community server's contact form attracts bot traffic, and an unfiltered webhook turns a channel into a spam feed that members mute.

Related guides