EdgeSubmit Documentation

Add form submissions to any website. No backend required.

Base URL: https://api.edgesubmit.com
All API endpoints accept and return JSON. CORS is enabled for all origins by default.

Quick Start

Get form submissions working in 2 minutes:

  1. Create an account — Go to the dashboard and enter your email.
  2. Copy your API key — You'll get an API key that starts with es_.
  3. 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

FieldTypeRequiredDescription
access_keystringYesYour EdgeSubmit API key
[any field]stringNoAny form field — all are included in the email

Content Types

  • application/json — JSON body
  • application/x-www-form-urlencoded — Standard HTML form
  • multipart/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.

FieldDescription
access_keyYour API key (required)
_subjectCustom email subject line
_redirectURL to redirect to after submission (for HTML forms)
_replytoSet the reply-to address on the notification email
_from_nameCustom "from" name on the notification email
_honeypotHidden 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

FieldRequiredDescription
urlYesThe HTTPS endpoint to receive submissions
secretNoHMAC-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

  1. EdgeSubmit searches HubSpot for an existing contact by email
  2. If found, the contact is updated with the new form data
  3. If not found, a new contact is created

Field Mapping

Form FieldHubSpot Property
emailemail
nameSplit into firstname / lastname
phonephone
companycompany

HubSpot Configuration

FieldRequiredDescription
api_keyYesYour 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

  1. A new Person is created in Pipedrive with the form's name, email, and phone
  2. If the submission includes a company or subject field, a Deal is also created and linked to the person

Pipedrive Configuration

FieldRequiredDescription
api_tokenYesYour Pipedrive API token. Find it in Settings → Personal preferences → API.
domainYesYour 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

  1. Create an Incoming Webhook in your Slack workspace (Apps → Incoming Webhooks → Add to Slack)
  2. Choose the channel to post to and copy the webhook URL
  3. Add the integration in EdgeSubmit with the webhook URL

Each submission posts a message with the form fields, source URL, and timestamp.

Slack Configuration

FieldRequiredDescription
webhook_urlYesYour Slack Incoming Webhook URL (starts with https://hooks.slack.com/)
channelNoOverride the default channel (e.g. #leads)

Discord

Post a rich embed notification to any Discord channel. Uses Discord's Webhooks.

How It Works

  1. In your Discord server, go to Channel Settings → Integrations → Webhooks → New Webhook
  2. Copy the webhook URL
  3. Add the integration in EdgeSubmit

Submissions are posted as rich embeds with form fields displayed inline.

Discord Configuration

FieldRequiredDescription
webhook_urlYesYour 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

  1. In your Teams channel, click ⋯ → Connectors → Incoming Webhook → Configure
  2. Name the webhook and copy the URL
  3. Add the integration in EdgeSubmit

Submissions are posted as MessageCards with form fields displayed as facts.

Teams Configuration

FieldRequiredDescription
webhook_urlYesYour 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

  1. Create a Notion integration and copy the API key
  2. Share your target database with the integration
  3. Copy the database ID from the URL
  4. Add the integration in EdgeSubmit

Field Mapping

Form FieldNotion Property Type
nameTitle
emailEmail
phonePhone
All other fieldsRich Text
source_urlURL (auto-added)

Notion Configuration

FieldRequiredDescription
api_keyYesYour Notion integration secret (starts with secret_)
database_idYesThe 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

  1. Generate a Personal Access Token with data.records:write scope
  2. Get your Base ID from the API docs
  3. Make sure your table has columns matching your form fields (capitalized)
  4. Add the integration in EdgeSubmit

Airtable Configuration

FieldRequiredDescription
api_keyYesYour Airtable Personal Access Token (starts with pat)
base_idYesThe Base ID (starts with app)
table_nameYesThe 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

  1. Create a Service Account in Google Cloud Console
  2. Enable the Google Sheets API for your project
  3. Download the service account JSON key file
  4. Share your Google Sheet with the service account email (Editor access)
  5. Add the integration in EdgeSubmit with the JSON key and spreadsheet ID

Google Sheets Configuration

FieldRequiredDescription
service_account_jsonYesThe full JSON key file contents from your Google service account
spreadsheet_idYesThe spreadsheet ID from the Google Sheets URL
sheet_nameNoSheet 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

FieldTypeRequiredDescription
typestringYeswebhook, hubspot, pipedrive, slack, discord, teams, notion, airtable, or google_sheets
namestringNoDisplay name (defaults to type)
configobjectYesType-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

PlanMonthly SubmissionsRate (per minute)
Free2505
Pro2,50030
Business100,000100

When you hit a rate limit, the API returns a 429 status. Monthly limits reset on your account anniversary date.

Error Codes

StatusErrorDescription
400Invalid request bodyCould not parse the request
401Missing access_keyNo API key in form data
401Invalid API keyKey not found or inactive
429Rate limit exceededToo many requests per minute
429Monthly limit reachedUpgrade plan for more submissions
500Internal server errorSomething went wrong on our end