How it works
New to emcognito? Learn how it works on our homepage, or create a free account to get started.
emcognito exposes a single public endpoint: POST https://api.ec.emcognito.com/subscribe. Send a JSON body with an email field and include your X-Publishable-Key header. That's it — the subscriber is recorded as pending, sent a confirmation email, and scoped to your account after they confirm. Get your publishable key from the owner portal after signing up.
Lead metadata: optional name, provider, spend_band, region, workload, and notes fields let you qualify signups without running a separate intake API. They appear in the portal, CSV export, REST API responses, and webhook payloads.
Double opt-in: emcognito sends the confirmation email and hosts the confirmation page — you don't build either. Subscribers stay pending until they click the link; a subscriber.confirmed webhook (or status: "verified" from the developer API) is your signal that they're verified.
One-click unsubscribe: every email carries List-Unsubscribe / List-Unsubscribe-Post headers (RFC 8058) and a hosted unsubscribe page — the bulk-sender requirement Gmail and Yahoo enforce. When someone opts out we hard-delete them and fire a subscriber.unsubscribed webhook so your synced copy stays clean. You build nothing.
The publishable key (pk_live_…) is safe in client-side code, so the framework snippets below run in the browser. The server-side examples (Next.js, Nuxt, SvelteKit, Astro) are optional — use them only if you prefer to keep the key out of your bundle.
https://api.ec.emcognito.com/subscribe| Field | Type | Description |
|---|---|---|
email | string | The subscriber's email address (required) |
name | string | The subscriber's name (optional) |
provider | string | Provider, platform, or vendor the lead uses (optional) |
spend_band | string | Budget or usage band for qualification (optional) |
region | string | Lead geography or target market (optional) |
workload | string | Primary use case or workload (optional) |
notes | string | Free-form lead notes, up to 1,000 characters (optional) |
| Header | Description |
|---|---|
X-Publishable-Key | Your account's publishable API key (safe to include in client-side code) |
<form id="subscribe-form">
<input type="email" id="email-input" placeholder="you@example.com" required />
<button type="submit">Subscribe</button>
<p id="subscribe-msg"></p>
</form>
<script>
document.getElementById('subscribe-form').addEventListener('submit', async (e) => {
e.preventDefault();
const email = document.getElementById('email-input').value;
const msg = document.getElementById('subscribe-msg');
try {
const res = await fetch('https://api.ec.emcognito.com/subscribe', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Publishable-Key': 'YOUR_PUBLISHABLE_KEY',
},
body: JSON.stringify({
email,
// Optional lead metadata:
// name, provider, spend_band, region, workload, notes
}),
});
if (!res.ok) throw new Error(await res.text());
msg.textContent = 'Check your inbox to confirm your subscription.';
} catch (err) {
msg.textContent = 'Something went wrong. Please try again.';
}
});
</script>Developer API & webhooks
Beyond collecting subscribers, you can read your list from your own server and receive real-time events — so confirmed subscribers flow straight into your ESP, CRM, or data warehouse. Create secret keys and webhook endpoints on the Developers page in your portal.
| Key | Type | Where it lives | Used for |
|---|---|---|---|
pk_live_… | Publishable | Safe in client-side code | POST /subscribe |
sk_live_… | Secret | Server-side only — never in a browser | /v1/* read & write |
Read subscribers (REST)
Authenticate with a server-side secret key (sk_live_…). The origin is derived from the key, so a key can only read its own site's subscribers. Secret keys are server-side only — never expose one in a browser.
GET /v1/subscribers— list (status,limit,starting_after)GET /v1/subscribers/{email}— fetch onePOST /v1/subscribers— add one (status:subscribedorpending; accepts the same optional lead metadata as/subscribe)DELETE /v1/subscribers/{email}— remove one
curl https://api.ec.emcognito.com/v1/subscribers \
-H "Authorization: Bearer sk_live_..."Webhook events
We POST a signed JSON event to your endpoint for each of:
subscriber.confirmed— completed double opt-insubscriber.created— signed up, not yet confirmedsubscriber.deleted— removed from the listsubscriber.unsubscribed— self-removed via one-click unsubscribesubscriber.bounced— address permanently bouncedsubscriber.complained— marked as spam
Each delivery carries Emcognito-Signature: t=<unix>,v1=<hex>. Verify it by recomputing HMAC-SHA256(secret, "{t}.{raw_body}") over the raw body, comparing in constant time, and rejecting stale timestamps. Failed deliveries are retried with backoff; endpoints auto-disable after repeated failures.
import crypto from 'node:crypto'
const header = req.get('Emcognito-Signature') || '' // "t=...,v1=..."
const p = Object.fromEntries(header.split(',').map(s => s.split('=')))
const expected = crypto
.createHmac('sha256', process.env.EMCOGNITO_WEBHOOK_SECRET)
.update(p.t + '.' + rawBody) // rawBody = the unparsed request body
.digest('hex')
const ok = p.v1 && crypto.timingSafeEqual(
Buffer.from(expected), Buffer.from(p.v1))
if (!ok || Math.abs(Date.now() / 1000 - Number(p.t)) > 300) reject()import hmac, hashlib, time
header = request.headers.get("Emcognito-Signature", "")
p = dict(kv.split("=", 1) for kv in header.split(",") if "=" in kv)
expected = hmac.new(SECRET, f'{p["t"]}.{raw_body}'.encode(),
hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected, p.get("v1", "")):
reject()
if abs(time.time() - int(p["t"])) > 300:
reject() # stale timestampErrors & status codes
Every error response is JSON: { "detail": "<message>" }. Handle these in your integration:
POST /subscribe
| Status | Meaning |
|---|---|
201 | Accepted — confirmation email sent; the subscriber stays pending until they click it (double opt-in) |
400 | X-Publishable-Key header missing |
403 | Publishable key not recognized |
422 | Invalid email address |
503 | Temporary backend/email error — safe to retry |
Developer API (/v1/*, secret key)
| Status | Meaning |
|---|---|
401 | Missing, invalid, or revoked secret key |
403 | Key is missing the required scope (subscribers:read / subscribers:write) |
404 | No subscriber with that email |
400 | Invalid cursor or request body |
503 | Temporary backend error — safe to retry |
Rate limits: /subscribe and the /v1 endpoints are not per-identity rate limited (a pending address is re-emailed at most once per 60 s but still returns 201). The account-email endpoints — signup and magic-link request — are throttled per email and may return 429 with no Retry-After header, so back off and retry on a fixed delay.
Ready to start collecting subscribers?
Create a free account to get your publishable key. Free during beta.
Create free account