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.
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.
your form ──POST──▶ ShipMyForm ──▶ spam filter ──▶ stored ──▶ Discord
(holds the webhook URL, builds the embed)The two routes at a glance
| Managed Discord connector | Webhook + automation | |
|---|---|---|
| Setup | Paste one Discord webhook URL | Wire an automation to reshape the payload |
| Plan | Paid (Starter and up) | Free plan works |
| Message formatting | Embed, built for you | You build the Discord message yourself |
| Webhook URL | Stored server-side, never in your page | Stored in your automation |
| Maintenance | None | You 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
<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:
- Open the target channel's settings (the cog beside the channel name).
- Go to Integrations → Webhooks → New Webhook.
- Give it a name — this is the default display name, though ShipMyForm overrides it per message — and confirm the channel.
- 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:
https://discord.com/api/webhooks/000000/xxxxxxxx?thread_id=123456789One 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:
| Limit | Value |
|---|---|
| Fields per embed | 25 |
| Field name | 256 characters |
| Field value | 1,024 characters |
| Whole embed, all parts summed | 6,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.
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 Contenton success — an empty body is the success case, not a failure. Append?wait=trueif 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
429with aretry_aftervalue 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 a429is 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:
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:
{
"content": "📨 New submission from [email protected]: Loved the guide, can we talk?"
}For something closer to the managed connector's output, send an embed:
{
"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.
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
- Team on Slack as well as Discord? Send submissions to Slack works the same way.
- New to all of this? Start with what a form backend actually is.
- Full setup reference: the Discord connector docs.
- Want submissions as records you can triage, not just messages? Notion and Airtable.
- Running a public community form? Why your contact form gets spam explains what is hitting it.
- Start free — 250 submissions a month, the webhook connector included, no card required.
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
How to Send Form Submissions to Slack
Get each form submission into Slack as a clean, formatted message. Use the managed Slack connector, or wire it up free with the webhook connector and an automation.
How to Connect Your Form to n8n
Send every form submission straight into an n8n workflow with the free webhook connector. No serverless glue, no polling.
What Is a Form Backend? (And When You Need One)
The plain-language definition, how form endpoints work, and when a hosted backend beats rolling your own.