Silo

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

CallWhat it does
GET /api/meChecks your key and returns your account name
GET /api/lists, /api/tags, /api/sequencesWhat exists in your account
POST /api/subscribersCreate or update a subscriber on a list, with tags
POST /api/tags/add, /api/tags/removeTag or untag a subscriber
POST /api/enrollmentsStart someone on a sequence
POST /automations/eventsFire 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

  1. Read the raw request body as bytes, before parsing JSON.
  2. Split X-Silo-Signature into t and v1.
  3. Reject the request if t is more than 300 seconds from now.
  4. Compute HMAC-SHA256 of "<t>." + raw body with your endpoint's secret, as lowercase hex, and compare it with v1 in 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));
}