Event registration7 min read
Event registration without Eventbrite, for free community events
Run registration for a free community event on your own site: session choices, a confirmation email from your own tool, room counts and a check-in list.
For a free event, a registration form on your own site can replace Eventbrite's signup: it posts to Ikiform, which stores each registration and emails your team. Add a webhook that sends the confirmation email from your own email provider, and count seats per session from the latest registration per email. Keep Eventbrite, or another ticketing platform, for paid tickets, hard capacity caps and check-in scanning.
What's different without a ticketing platform
Eventbrite does five jobs at once, and it's easy to forget the ones you'll now do yourself. For a free community day, a meetup series or a workshop, most of them are small. Here's the split, honestly:
- The event page moves from Eventbrite's page to your own site, with your design.
- Registration moves from Eventbrite's checkout to your form, which posts to Ikiform.
- The confirmation email is no longer sent for you. A webhook sends it from your own email provider.
- Capacity is no longer a hard cap. You count registrations per session and pause the form when you're full.
- Check-in is no longer an app with QR scanning. You print a list from the CSV export.
Eventbrite's pricing changes, so check its own pricing page before you compare costs. For free events the reasons to leave are usually not fees: the attendee list sits on someone else's platform, the page looks like theirs, and registrants hand their details to another company with its own account and email settings. For paid events, stay. Ikiform has no payments.
A registration form with session choices
A community day often has parallel sessions, and rooms differ in size. Ask which sessions people plan to attend, so you can move a popular workshop to a bigger room before the day. This definition caps choices at three of four, since two run at the same time:
Preview
Runs Ikiform's validator in your browser. Nothing is sent.
sessions is a multi_select, stored as an array. In HTML, name the checkboxes sessions[] so a single choice is still stored as a list.nameis labelled for the badge, which gets you the name people want to be called.lunchis aradiowith aNo lunchoption, so you order for the people who'll eat.updatesis an unticked checkbox, stored astrueorfalseon a submission that keeps its owncreated_at. Only add people to your newsletter when it'strue.- Use the RSVP form from the main guide for the markup in your stack, with these fields swapped in.
Send the confirmation from your own email tool
Ikiform doesn't email the person who fills in the form. Notification emails go to you and your team only, so there is no autoresponder, confirmation email or double opt-in. Send those from your email tool, triggered by a webhook. For an event that matters, because people forward the confirmation to their manager and search for it on the morning. This route handler receives Ikiform's signed webhook and sends the email through Resend's API. Any provider with a send endpoint works the same way.
import { verifyWebhook, WebhookVerificationError } from "@ikiform/sdk/webhooks";
const TITLES: Record<string, string> = {
keynote: "Opening talk, 10:00",
workshop_a: "Workshop A, 11:30",
workshop_b: "Workshop B, 11:30",
panel: "Closing panel, 15:00",
};
function escape(text: string) {
return text.replace(/[&<>"']/g, (char) => `&#${char.charCodeAt(0)};`);
}
export async function POST(request: Request) {
const payload = await request.text();
try {
const event = await verifyWebhook({
payload,
headers: request.headers,
secret: process.env.IKIFORM_WEBHOOK_SECRET!,
});
if (event.type !== "submission.created") return new Response(null, { status: 204 });
const fields = event.data.fields;
const email = String(fields.email ?? "");
const chosen = (Array.isArray(fields.sessions) ? fields.sessions : []).map((id) => TITLES[String(id)] ?? String(id));
const response = await fetch("https://api.resend.com/emails", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.RESEND_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": request.headers.get("webhook-id") ?? event.data.submission_id,
},
body: JSON.stringify({
from: "Community Day <hello@community.example>",
to: email,
reply_to: "hello@community.example",
subject: "You're registered for Community Day, 14 March",
html: [
`<p>Hi ${escape(String(fields.name ?? ""))},</p>`,
"<p>You're on the list for Community Day on 14 March at The Old Library. Doors open at 9:30.</p>",
`<p>Your sessions: ${chosen.map(escape).join(", ")}.</p>`,
'<p><a href="https://community.example/day.ics">Add it to your calendar</a>. Can\'t come after all? Reply to this email.</p>',
].join(""),
}),
});
if (!response.ok) return new Response(`Email API answered ${response.status}`, { status: 502 });
return new Response(null, { status: 204 });
} catch (error) {
if (error instanceof WebhookVerificationError) return new Response(error.reason, { status: 400 });
throw error;
}
}verifyWebhookchecks the signature, so nobody else can make your domain send email.Idempotency-Keyuses the delivery'swebhook-id, which stays the same on every automatic retry and on a manual Retry of a failed delivery, so a delivery whose answer got lost doesn't send a second email.- The name and session titles are escaped before they go into HTML. Treat field values as untrusted input.
- A
502makes Ikiform retry, so a short outage at the email provider doesn't lose a confirmation. Spam never triggers the webhook, so bots don't make you email strangers.
Add the route under Webhooks in the dashboard and copy its secret into IKIFORM_WEBHOOK_SECRET. Ikiform retries a failed delivery for about 3 days, and the delivery log shows each attempt. Without code, an automation tool such as Zapier or Make can send the same email.
Seats per session
Ikiform won't stop registrations for one session when its room is full, and it can't hide a full option. Count instead, using each person's newest registration, and run it whenever you want a number:
import { createClient } from "@ikiform/sdk";
const ikiform = createClient({ apiKey: process.env.IKIFORM_SECRET_KEY! });
const ROOMS: Record<string, number> = { keynote: 200, workshop_a: 30, workshop_b: 30, panel: 200 };
const latest = new Map<string, string[]>();
for await (const submission of ikiform.submissions.iterate({ formId: process.argv[2], status: "received", limit: 100 })) {
const email = String(submission.fields.email ?? "").trim().toLowerCase();
const chosen = Array.isArray(submission.fields.sessions) ? submission.fields.sessions.map(String) : [];
if (email && !latest.has(email)) latest.set(email, chosen);
}
for (const [session, seats] of Object.entries(ROOMS)) {
const taken = [...latest.values()].filter((chosen) => chosen.includes(session)).length;
console.log(`${session.padEnd(12)} ${String(taken).padStart(4)} of ${seats}${taken >= seats ? " FULL" : ""}`);
}When a workshop fills, edit the page to mark it full and remove its option from the form and the field definition, so new posts can't choose it. When the whole day fills, choose Pause form: posts get 403 form_paused, and the page should say registration has closed.
| Situation | What to do |
|---|---|
| One workshop full | Remove its option from the form and the field definition, and say so on the page |
| The whole day full | Pause form and replace the form with a waitlist email address |
| Someone cancels | They reply to the confirmation email. Delete their submission so the count frees the seat |
| No-shows expected | Free events often see a share of no-shows. Track your own rate and over-register a little next time |
At the door
- The evening before, export Submissions as CSV and remove duplicate emails, keeping the newest.
- Sort by name, keep
name,organisationandlunch, and print two copies for the desk. - Print badges from the same sheet with a mail merge, using the
namecolumn. - Write in walk-ins by hand. After the event, add them to your own list only if they agree.
Check it before you announce
- Under the form's Allowed domains, add your site's domain, plus
localhostwhile you develop. Leave it empty to accept posts from anywhere. - Register with your own email, choosing two sessions.
- The confirmation email arrives from your domain, listing both sessions.
- If you added a webhook, open the endpoint in Webhooks. The delivery shows
succeededwith your server's answer. - Run the seat count. Each of your two sessions shows one seat taken.
- Nothing arrived? Switch the dashboard's status filter to Spam. Spam gets the same success answer as a real submission.
Gotchas
| Problem | What to do |
|---|---|
422 saying to choose at most 3 options | Someone picked all four sessions. That's the max_items rule working; show Ikiform's message next to the checkboxes. |
| Only one session is stored when someone picks one | Name the checkboxes sessions[]. With the field defined as multi_select, a single value is still stored as a list. |
| No confirmation email, but the registration is in Ikiform | Open the delivery in the endpoint's log. A 502 with the provider's status means it refused the email, often an unverified sending domain. |
| The submission is in the dashboard but no email or webhook arrived | It was stored as spam. Spam sends nothing. Open it under the Spam filter to see the reasons, and mark it as not spam to send its email and webhooks. |
429 rate_limited | One visitor sent more than the form's rate limit allows. Standard allows 5 a minute per visitor. Wait for Retry-After, or choose Relaxed or Custom for forms that post from one shared IP. |
Questions
For a free event where you can count seats and print a list, yes. For paid tickets, hard capacity limits, waitlists or QR check-in, a ticketing platform does those and Ikiform doesn't.
Send it yourself. Add an Ikiform webhook that calls your email provider's API, or connects through an automation tool, and send the confirmation from your own domain.
Not automatically. Count registrations per session with a short script or a sheet, and remove a full workshop's option from the form when it fills.
No. Every accepted post is stored. An identical repeat within 24 hours adds to the spam score but isn't spam on its own, so deduplicate by email in your list, database or CSV export.
No, there is no free plan. Testing is free: you can send 50 test submissions a month from localhost before you pay. To go live, Pro starts at $6/mo for 500 submissions, or you can pay once for 10,000 submissions for $49 or 50,000 submissions for $149, with credits that never expire.
Stuck on a step, or want a guide for your situation? Email hello@ikiform.com.