Guide
API & webhooks basics
The REST API lets your own systems add people to Silo and trigger automations. Webhooks tell your systems when something happens in Silo. Both are Zapier-ready: you can connect them today using Zapier's webhook steps. (A Silo app in the Zapier directory is not published yet.)
Authentication
Create an API key in Settings > API. It starts with sk_ and only ever acts on your own account. Send it with every request:
X-API-Key: sk_your_key_here
Keep it on your server. Anyone with the key can add and change subscribers in your account. You can regenerate it at any time; the old key stops working immediately.
Common calls
All requests and responses are JSON. Base URL: https://app.silomailpro.com
| Call | What it does |
|---|---|
GET /api/me | Checks your key and returns your account name |
GET /api/lists, /api/tags, /api/sequences | What exists in your account |
POST /api/subscribers | Create or update a subscriber on a list, with tags |
POST /api/tags/add, /api/tags/remove | Tag or untag a subscriber |
POST /api/enrollments | Start someone on a sequence |
POST /automations/events | Fire an "API event" automation trigger |
POST /api/hooks, DELETE /api/hooks/{id} | Subscribe or unsubscribe a webhook (REST hooks) |
Add a subscriber
curl https://app.silomailpro.com/api/subscribers \
-H "X-API-Key: sk_your_key_here" \
-H "Content-Type: application/json" \
-d '{"email": "ada@example.com", "list": "newsletter",
"first_name": "Ada", "tags": ["trial"],
"consent_source": "pricing-page-form"}'
Silo never re-subscribes someone through the API who unsubscribed, bounced or complained; the response says so, and that person has to opt in again through a signup page. Record where consent came from in consent_source.
Trigger an automation
curl https://app.silomailpro.com/automations/events \
-H "X-API-Key: sk_your_key_here" \
-H "Content-Type: application/json" \
-d '{"email": "ada@example.com", "event": "trial_started",
"merge_vars": {"plan": "Pro"}}'
Any automation whose trigger is API event with the name trial_started starts for that person.
Webhooks
Add endpoints in Integrations (https only, up to 25 per account) and choose which events each one receives: subscribed, unsubscribed, tag_added, bounced, complained, email_opened, email_clicked.
POST https://your-endpoint.example.com/silo
Content-Type: application/json
X-Silo-Timestamp: 1790000000
X-Silo-Signature: t=1790000000,v1=5f2b...e9
X-Silo-Delivery-Id: 1234
{"id": "evt_...", "event": "subscribed", "email": "ada@example.com", "list_key": "newsletter", ...}
Answer with a 2xx status within 10 seconds. Anything else, including redirects, is retried after roughly 1 minute, 5 minutes, 15 minutes, 1 hour and 6 hours, then dropped. X-Silo-Delivery-Id is the same on every retry, so you can ignore duplicates. Responding 410 Gone switches the endpoint off.
Verify the signature
- Read the raw request body as bytes, before parsing JSON.
- Split
X-Silo-Signatureintotandv1. - Reject the request if
tis more than 300 seconds from now. - Compute HMAC-SHA256 of
"<t>." + raw bodywith your endpoint's secret, as lowercase hex, and compare it withv1in constant time.
Python
import hashlib, hmac, time
def verify(secret: str, raw_body: bytes, header: str) -> bool:
parts = dict(p.split("=", 1) for p in header.split(","))
t = int(parts["t"])
if abs(time.time() - t) > 300:
return False
expected = hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, parts.get("v1", ""))
Node
const crypto = require("crypto");
function verify(secret, rawBody, header) {
const parts = Object.fromEntries(header.split(",").map(p => p.split("=")));
if (Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) return false;
const expected = crypto.createHmac("sha256", secret)
.update(`${parts.t}.`).update(rawBody).digest("hex");
return Boolean(parts.v1) && expected.length === parts.v1.length &&
crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}