EdgeSubmit Documentation
Add form submissions to any website. No backend required.
https://api.edgesubmit.comAll API endpoints accept and return JSON. CORS is enabled for all origins by default.
Quick Start
Get form submissions working in 2 minutes:
- Create an account — Go to the dashboard and enter your email.
- Copy your API key — You'll get an API key that starts with
es_. - Add the form — Point your HTML form to our endpoint:
<!-- Drop this into any HTML page -->
<form action="https://api.edgesubmit.com/submit" method="POST">
<input type="hidden" name="access_key" value="YOUR_API_KEY" />
<input type="text" name="name" required />
<input type="email" name="email" required />
<textarea name="message" required></textarea>
<button type="submit">Send</button>
</form>
That's it. Submissions will be emailed to the address tied to your API key.
Authentication
EdgeSubmit uses API keys for authentication. Include your API key as a form field named access_key in every submission.
Your API key starts with es_ and is 64+ characters long. Keep it in your frontend code — it's designed to be public-facing (like a Stripe publishable key). Origin restrictions can be configured per key for extra security.
For dashboard API endpoints (/api/*), use the user_id query parameter returned during registration.
POST /submit Submit a form
The main form submission endpoint. Accepts form data via JSON, URL-encoded, or multipart.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
access_key | string | Yes | Your EdgeSubmit API key |
[any field] | string | No | Any form field — all are included in the email |
Content Types
application/json— JSON bodyapplication/x-www-form-urlencoded— Standard HTML formmultipart/form-data— File upload forms
Success Response
{
"success": true,
"message": "Submission received"
}
Error Response
{
"success": false,
"error": "Missing access_key. Include your EdgeSubmit API key in the form data."
}
Special Fields
These optional fields control behavior. They are not included in the email body.
| Field | Description |
|---|---|
access_key | Your API key (required) |
_subject | Custom email subject line |
_redirect | URL to redirect to after submission (for HTML forms) |
_replyto | Set the reply-to address on the notification email |
_from_name | Custom "from" name on the notification email |
_honeypot | Hidden field — if filled, submission is silently dropped (spam protection) |
POST /api/register Create an account
Create a new account and get your first API key.
// Request
{
"email": "you@example.com",
"name": "Your Name"
}
// Response (201)
{
"ok": true,
"user_id": "abc123...",
"api_key": "es_xxxx...",
"plan": "free",
"monthly_limit": 250
}
POST /api/auth Login or register
Login or register in one call. If the email exists, returns existing user data. Otherwise creates a new account.
// Request
{
"email": "you@example.com",
"name": "Your Name" // optional, used on first registration
}
// Response — existing user
{
"ok": true,
"action": "login",
"user": { "id": "...", "email": "...", "plan": "free" }
}
// Response — new user (201)
{
"ok": true,
"action": "register",
"user": { "id": "...", "email": "...", "plan": "free" },
"api_key": "es_xxxx..."
}
GET /api/keys List API keys
List all API keys for your account.
GET /api/keys?user_id=YOUR_USER_ID
POST /api/keys Create a new key
Create a new API key.
{
"user_id": "your_user_id",
"to_email": "inbox@example.com",
"label": "My Website",
"allowed_origins": "https://mysite.com"
}
GET /api/submissions List submissions
List recent form submissions with pagination.
GET /api/submissions?user_id=YOUR_USER_ID&limit=50&offset=0
GET /api/stats Usage stats
Get submission counts and usage stats for the current billing period.
GET /api/stats?user_id=YOUR_USER_ID
Integrations
EdgeSubmit can automatically forward every form submission to external services — webhooks, CRMs (HubSpot, Pipedrive), chat platforms (Slack, Discord, Microsoft Teams), databases (Notion, Airtable), and spreadsheets (Google Sheets). Configure integrations from the Dashboard under the Integrations tab, or via the API below.
Each account can have up to 5 active integrations. When a form submission is received, all enabled integrations fire in parallel.
Webhook
Receive a POST request to any URL whenever a form is submitted. If you provide a signing secret, EdgeSubmit includes an HMAC-SHA256 signature in the X-EdgeSubmit-Signature header so you can verify authenticity.
Webhook Payload
// POST to your webhook URL
// Content-Type: application/json
// X-EdgeSubmit-Signature: <HMAC-SHA256 hex digest> (if secret configured)
{
"event": "submission",
"submission_id": "a1b2c3...",
"source_url": "https://mysite.com/contact",
"submitted_at": "2025-01-15T10:30:00Z",
"form_data": {
"name": "Jane Doe",
"email": "jane@example.com",
"message": "Hello!"
}
}
Verifying the Signature
If you set a Signing Secret, compute an HMAC-SHA256 of the raw request body using that secret and compare it to the X-EdgeSubmit-Signature header.
// Node.js verification example
const crypto = require("crypto");
function verifySignature(body, secret, signature) {
const expected = crypto
.createHmac("sha256", secret)
.update(body)
.digest("hex");
return expected === signature;
}
Webhook Configuration
| Field | Required | Description |
|---|---|---|
url | Yes | The HTTPS endpoint to receive submissions |
secret | No | HMAC-SHA256 signing secret for signature verification |
HubSpot
Automatically create or update contacts in HubSpot when a form is submitted. EdgeSubmit maps your form's email, name, phone, and company fields to HubSpot contact properties.
How It Works
- EdgeSubmit searches HubSpot for an existing contact by email
- If found, the contact is updated with the new form data
- If not found, a new contact is created
Field Mapping
| Form Field | HubSpot Property |
|---|---|
email | email |
name | Split into firstname / lastname |
phone | phone |
company | company |
HubSpot Configuration
| Field | Required | Description |
|---|---|---|
api_key | Yes | Your HubSpot Private App access token. Create one in Settings → Integrations → Private Apps with crm.objects.contacts.write scope. |
Pipedrive
Automatically create a person (and optionally a deal) in Pipedrive for each form submission.
How It Works
- A new Person is created in Pipedrive with the form's name, email, and phone
- If the submission includes a
companyorsubjectfield, a Deal is also created and linked to the person
Pipedrive Configuration
| Field | Required | Description |
|---|---|---|
api_token | Yes | Your Pipedrive API token. Find it in Settings → Personal preferences → API. |
domain | Yes | Your Pipedrive company domain (e.g. mycompany from mycompany.pipedrive.com) |
Slack
Post a formatted notification to any Slack channel whenever a form is submitted. Uses Slack's Incoming Webhooks.
How It Works
- Create an Incoming Webhook in your Slack workspace (Apps → Incoming Webhooks → Add to Slack)
- Choose the channel to post to and copy the webhook URL
- Add the integration in EdgeSubmit with the webhook URL
Each submission posts a message with the form fields, source URL, and timestamp.
Slack Configuration
| Field | Required | Description |
|---|---|---|
webhook_url | Yes | Your Slack Incoming Webhook URL (starts with https://hooks.slack.com/) |
channel | No | Override the default channel (e.g. #leads) |
Discord
Post a rich embed notification to any Discord channel. Uses Discord's Webhooks.
How It Works
- In your Discord server, go to Channel Settings → Integrations → Webhooks → New Webhook
- Copy the webhook URL
- Add the integration in EdgeSubmit
Submissions are posted as rich embeds with form fields displayed inline.
Discord Configuration
| Field | Required | Description |
|---|---|---|
webhook_url | Yes | Your Discord webhook URL (starts with https://discord.com/api/webhooks/) |
Microsoft Teams
Post a card notification to any Microsoft Teams channel. Uses Teams' Incoming Webhooks.
How It Works
- In your Teams channel, click ⋯ → Connectors → Incoming Webhook → Configure
- Name the webhook and copy the URL
- Add the integration in EdgeSubmit
Submissions are posted as MessageCards with form fields displayed as facts.
Teams Configuration
| Field | Required | Description |
|---|---|---|
webhook_url | Yes | Your Teams Incoming Webhook URL |
Notion
Create a new page in a Notion database for each submission. Form fields are automatically mapped to database properties.
How It Works
- Create a Notion integration and copy the API key
- Share your target database with the integration
- Copy the database ID from the URL
- Add the integration in EdgeSubmit
Field Mapping
| Form Field | Notion Property Type |
|---|---|
name | Title |
email | |
phone | Phone |
| All other fields | Rich Text |
| source_url | URL (auto-added) |
Notion Configuration
| Field | Required | Description |
|---|---|---|
api_key | Yes | Your Notion integration secret (starts with secret_) |
database_id | Yes | The ID of the target database (32-char hex from the URL) |
Airtable
Create a new record in an Airtable base for each submission. Form field names are capitalized and mapped to column names.
How It Works
- Generate a Personal Access Token with
data.records:writescope - Get your Base ID from the API docs
- Make sure your table has columns matching your form fields (capitalized)
- Add the integration in EdgeSubmit
Airtable Configuration
| Field | Required | Description |
|---|---|---|
api_key | Yes | Your Airtable Personal Access Token (starts with pat) |
base_id | Yes | The Base ID (starts with app) |
table_name | Yes | The name of the table to insert into |
Google Sheets
Append a row to a Google Sheet for each submission. Each submission adds a new row with the timestamp, source URL, and all form field values.
How It Works
- Create a Service Account in Google Cloud Console
- Enable the Google Sheets API for your project
- Download the service account JSON key file
- Share your Google Sheet with the service account email (Editor access)
- Add the integration in EdgeSubmit with the JSON key and spreadsheet ID
Google Sheets Configuration
| Field | Required | Description |
|---|---|---|
service_account_json | Yes | The full JSON key file contents from your Google service account |
spreadsheet_id | Yes | The spreadsheet ID from the Google Sheets URL |
sheet_name | No | Sheet tab name (defaults to Sheet1) |
Integrations API
Manage integrations programmatically. All endpoints require a Bearer token in the Authorization header.
GET /api/integrations List integrations
Returns all integrations for the authenticated user. Sensitive config values (keys, tokens, secrets) are masked.
// Response (200)
{
"ok": true,
"integrations": [
{
"id": 1,
"type": "webhook",
"name": "My Webhook",
"config": "{"url":"https://...","secret":"abc1****ef90"}",
"enabled": 1
}
]
}
POST /api/integrations Create an integration
Create a new integration. Maximum 5 per account.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
type | string | Yes | webhook, hubspot, pipedrive, slack, discord, teams, notion, airtable, or google_sheets |
name | string | No | Display name (defaults to type) |
config | object | Yes | Type-specific configuration (see above) |
// Webhook example
{
"type": "webhook",
"name": "Slack Notifier",
"config": {
"url": "https://myapp.com/hooks/edgesubmit",
"secret": "my_signing_secret"
}
}
// HubSpot example
{
"type": "hubspot",
"name": "CRM Sync",
"config": {
"api_key": "pat-na1-xxxx..."
}
}
// Pipedrive example
{
"type": "pipedrive",
"name": "Sales Pipeline",
"config": {
"api_token": "abc123...",
"domain": "mycompany"
}
}
// Slack example
{
"type": "slack",
"name": "Lead Alerts",
"config": {
"webhook_url": "https://hooks.slack.com/services/T.../B.../xxx"
}
}
// Discord example
{
"type": "discord",
"name": "Submissions Channel",
"config": {
"webhook_url": "https://discord.com/api/webhooks/123/abc..."
}
}
// Notion example
{
"type": "notion",
"name": "Leads Database",
"config": {
"api_key": "secret_abc123...",
"database_id": "a1b2c3d4e5f6..."
}
}
// Airtable example
{
"type": "airtable",
"name": "Contacts Table",
"config": {
"api_key": "patXXX...",
"base_id": "appXXX...",
"table_name": "Form Submissions"
}
}
// Google Sheets example
{
"type": "google_sheets",
"name": "Submissions Sheet",
"config": {
"service_account_json": "{"type":"service_account",...}",
"spreadsheet_id": "1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgVE2upms",
"sheet_name": "Leads"
}
}
// Response (201)
{ "ok": true, "message": "Integration created" }
PUT /api/integrations/:id Update an integration
Update an existing integration's name, config, or enabled status.
{
"name": "Updated Name",
"config": { "url": "https://new-endpoint.com/hook" },
"enabled": false
}
// Response (200)
{ "ok": true, "message": "Integration updated" }
DELETE /api/integrations/:id Delete an integration
Permanently remove an integration.
// Response (200)
{ "ok": true, "message": "Integration deleted" }
Framework Examples
HTML Form
<form action="https://api.edgesubmit.com/submit" method="POST">
<input type="hidden" name="access_key" value="YOUR_KEY" />
<input name="name" required />
<input name="email" type="email" required />
<textarea name="message" required></textarea>
<button type="submit">Send</button>
</form>
JavaScript (fetch)
const res = await fetch("https://api.edgesubmit.com/submit", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
access_key: "YOUR_KEY",
name: "Jane",
email: "jane@example.com",
message: "Hello!"
})
});
const data = await res.json();
React / Next.js
async function handleSubmit(e) {
e.preventDefault();
const form = new FormData(e.target);
form.append("access_key", "YOUR_KEY");
const res = await fetch("https://api.edgesubmit.com/submit", {
method: "POST", body: form
});
const data = await res.json();
}
cURL
curl -X POST https://api.edgesubmit.com/submit \
-H "Content-Type: application/json" \
-d '{"access_key":"YOUR_KEY","name":"Test","email":"t@t.com","message":"Hi"}'
Rate Limits
| Plan | Monthly Submissions | Rate (per minute) |
|---|---|---|
| Free | 250 | 5 |
| Pro | 2,500 | 30 |
| Business | 100,000 | 100 |
When you hit a rate limit, the API returns a 429 status. Monthly limits reset on your account anniversary date.
Error Codes
| Status | Error | Description |
|---|---|---|
400 | Invalid request body | Could not parse the request |
401 | Missing access_key | No API key in form data |
401 | Invalid API key | Key not found or inactive |
429 | Rate limit exceeded | Too many requests per minute |
429 | Monthly limit reached | Upgrade plan for more submissions |
500 | Internal server error | Something went wrong on our end |