Receive SmartForm webhook events and forward every new submission to a Slack channel as a Block Kit message.
SmartForm POSTs a JSON event to your webhook URL on every submission. The body mirrors the same field structure as the public form endpoint:
Your submission fields �name, email, message, etc.
exactly as the form sent them.
Reserved fields �names starting with _ are stripped before
the webhook fires (they are control fields, not user data). The
honeypot drop (_gotcha filled) means the webhook is not called
�the submission is silently discarded.
| Field | In webhook payload? | Notes |
|---|---|---|
_gotcha |
No | Drop trigger �never sent. |
_next |
No | UX control, stripped. |
_subject |
No | UX control, stripped. |
submission_id |
Yes (added) | Server-generated UUID for idempotency. |
is_spam |
Yes (added) | AI classification result. |
intent |
Yes (added) | sales / support / inquiry / spam (Pro). |
timestamp |
Yes (added) | Server time in ISO 8601. |
Field names are Formspree-compatible �the SmartForm form endpoint and your webhook use the same conventions.
Browser � SmartForm AI � POST this Vercel function � Slack incoming webhook
â”?
└─ verifies X-SmartForm-Signature
before forwarding
- Create a form in https://usesmartform.com/dashboard.
- Set the form's Webhook URL to your deployed Vercel function URL
(e.g.
https://smartform-slack.vercel.app/api/webhook). - SmartForm posts every new submission to that URL (signed with HMAC-SHA256).
- This function verifies the signature, builds a Slack Block Kit message, and forwards to Slack.
- Create a Slack incoming webhook: https://api.slack.com/messaging/webhooks �pick a channel �Copy URL.
- Clone, install, configure, deploy:
git clone https://github.com/smartformai/smartform-example-webhook-slack.git cd smartform-example-webhook-slack npm install vercel link vercel env add SLACK_WEBHOOK_URL # paste the URL from step 1 vercel env add SMARTFORM_HMAC_SECRET # from your SmartForm workspace settings vercel deploy --prod
- In SmartForm dashboard, set the form's Webhook URL to
https://<your-deployment>.vercel.app/api/webhook.
// api/webhook.ts �Vercel Edge Function
export const config = { runtime: 'edge' };
export default async function handler(req: Request) {
if (req.method !== 'POST') return new Response('Method Not Allowed', { status: 405 });
const body = await req.text();
if (!verifySignature(body, req.headers.get('X-SmartForm-Signature'))) {
return new Response('Invalid signature', { status: 401 });
}
const event = JSON.parse(body);
if (event.event !== 'submission.created') return new Response('ok', { status: 200 });
const s = event.submission;
const fields = Object.entries(s.data || {})
.filter(([k]) => !k.startsWith('_'))
.slice(0, 10)
.map(([k, v]) => ({ type: 'mrkdwn', text: `*${k}*\n${String(v).slice(0, 800)}` }));
const blocks = [
{ type: 'header', text: { type: 'plain_text',
text: s.is_high_value ? '🔥 High-value lead' : (s.is_spam ? '🛡�Spam submission' : 'New form submission') } },
{ type: 'section', text: { type: 'mrkdwn', text: s.ai_summary || '_(no AI summary)_' } },
...(fields.length ? [{ type: 'section', fields }] : []),
{ type: 'context', elements: [
{ type: 'mrkdwn', text: `form=\`${event.form.form_id}\` intent=\`${s.intent_label}\` (${s.intent_confidence.toFixed(2)}) spam=${s.spam_confidence.toFixed(2)}` }] },
];
await fetch(process.env.SLACK_WEBHOOK_URL!, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ blocks }),
});
return new Response('ok', { status: 200 });
}verifySignature() is in lib/verify.ts �it recomputes HMAC-SHA256 with your shared
secret and compares it to the X-SmartForm-Signature: sha256=<hex> header.
{
"event": "submission.created",
"submitted_at": "2026-09-28T07:54:00Z",
"form": { "id": "...", "form_id": "f_abc12345", "name": "Contact form" },
"submission": {
"id": "sub_01HXX...",
"data": { "name": "Ada", "email": "[email protected]", "message": "Hi!" },
"is_spam": false,
"spam_confidence": 0.02,
"intent_label": "sales",
"intent_confidence": 0.93,
"is_high_value": true,
"ai_summary": "Visitor asks about pricing; mentions a 5-person team.",
"ip_address": "203.0.113.5",
"user_agent": "Mozilla/5.0 ..."
}
}Header: X-SmartForm-Signature: sha256=<hmac_hex>
npx vercel dev
curl -X POST http://localhost:3000/api/webhook \
-H 'Content-Type: application/json' \
-H 'X-SmartForm-Signature: sha256=<compute locally with your secret>' \
-d @sample-payload.jsonYes. AI spam filtering is enabled by default on every plan. AI intent classification and high-value lead detection require a paid plan (Pro or Business) �the dashboard enforces this and returns HTTP 402 if you try to enable them on a free workspace.
No. The form posts directly to a public endpoint using only an 8-char
form ID, which is non-enumerable. The example also includes a hidden
_gotcha honeypot field so naive bots cannot submit.
No. This is a Slack-format sender (it POSTs to a Slack-compatible webhook after receiving a SmartForm event), not a Slack event receiver. For receiver-side verification, see the SmartForm docs.
Discord webhook example | SmartForm JS SDK
MIT.