Connect Zapier & Make
Every VeloCMS blog or store can wire itself into 7,000+ other apps without a line of code. This page documents the exact contract — event catalog, subscribe/unsubscribe lifecycle, polling fallback, and signature verification — that both platforms need. You do not need to wait for a published VeloCMS app in either directory: Zapier's built-in "Webhooks by Zapier" trigger and Make's generic HTTP module both work against this contract today.
How it works#
VeloCMS ships a platform-agnostic integration core — a stable event catalog, a signed delivery envelope, and a full REST hook subscription lifecycle. Zapier and Make ride that same core; nothing about their integration is a separate system. Pick a trigger event (say order.paid), subscribe a webhook URL to it, and every future occurrence gets delivered as an HMAC-signed JSON payload within seconds.
Two delivery models are available side by side. A REST hook subscription pushes events to your URL the moment they happen — this is what Zapier's platform prefers and what Make's Instant Trigger uses. A polling endpoint lets either platform pull recent occurrences on a schedule instead — useful for a Zap's "Test" step before a hook has ever fired, and it is Make's default module behavior unless you wire an Instant Trigger explicitly.
Event catalog#
Every event VeloCMS can dispatch is one flat, dot-namespaced name. Subscribe to as many as you need on a single webhook — the same list populates the trigger dropdown a published Zapier app would show.
| Event | Category |
|---|---|
| post.created | Content |
| post.updated | Content |
| post.published | Content |
| post.unpublished | Content |
| post.deleted | Content |
| page.published | Content |
| member.subscribed | Members |
| member.unsubscribed | Members |
| member.tier_changed | Members |
| comment.posted | Comments |
| comment.approved | Comments |
| comment.deleted | Comments |
| order.paid | Commerce |
| order.refunded | Commerce |
| order.shipped | Commerce |
| product.created | Commerce |
| product.updated | Commerce |
| cart.abandoned | Commerce |
| gift_card.issued | Commerce |
| media.uploaded | Media |
| test.ping | System |
The machine-readable version of this table — with full payload schemas per event — is always current at /api/v1/asyncapi.json (AsyncAPI 3.0). REST endpoints for CRUD access are documented at /api/v1/openapi.json (OpenAPI 3.1).
REST hook lifecycle#
This maps 1:1 to the REST Hook contract Zapier's platform expects — subscribe with a POST, confirm the subscription still exists with a GET /{id} (Zapier calls this "checkExists"), and remove it with a DELETE. All three calls require an API key with the webhooks:write scope (checkExists only needs webhooks:read).
# Subscribe — Zapier calls this when a Zap is turned on
curl -X POST https://<your-blog>.velocms.org/api/v1/webhooks \
-H "Authorization: Bearer velo_<your_api_key>" \
-H "Content-Type: application/json" \
-d '{
"name": "Zapier order trigger",
"url": "https://hooks.zapier.com/hooks/catch/<id>/<hook>/",
"events": ["order.paid"]
}'
# → 201 { "id": "wh_abc", "secret": "<hmac-signing-secret, shown once>", ... }
# Check the subscription still exists — Zapier calls this periodically
curl https://<your-blog>.velocms.org/api/v1/webhooks/wh_abc \
-H "Authorization: Bearer velo_<your_api_key>"
# Unsubscribe — Zapier calls this when a Zap is turned off or deleted
curl -X DELETE https://<your-blog>.velocms.org/api/v1/webhooks/wh_abc \
-H "Authorization: Bearer velo_<your_api_key>"
# → 204 No ContentA Zap's "Test" step calls the test-fire endpoint, which delivers a synthetic test.ping event to your URL using the real HMAC signing — so you can verify signature checking without waiting for a real order:
curl -X POST https://<your-blog>.velocms.org/api/v1/webhooks/wh_abc/test \
-H "Authorization: Bearer velo_<your_api_key>"
# → 200 { "webhook_id": "wh_abc", "event": "test.ping", "success": true, "status_code": 200, ... }Polling fallback#
Every trigger also offers a polling-fallback endpoint — read-only, no side effects, reads the existing delivery audit log. Results are always sorted newest-first, and every item carries a field named literally id — Zapier's platform de-duplicates polling results strictly by that field, so it must be stable across repeated polls of the same underlying delivery.
curl "https://<your-blog>.velocms.org/api/v1/webhooks/events?event=order.paid" \
-H "Authorization: Bearer velo_<your_api_key>"
# → 200 {
# "items": [
# { "id": "550e8400-...", "event": "order.paid", "created": 1751500000, "data": { ... } },
# { "id": "8a7b6c5d-...", "event": "order.paid", "created": 1751490000, "data": { ... } }
# ]
# }
# Optional cursor — only rows created after this ISO-8601 timestamp
curl "https://<your-blog>.velocms.org/api/v1/webhooks/events?event=order.paid&since=2026-07-01T00:00:00Z" \
-H "Authorization: Bearer velo_<your_api_key>"Requires the webhooks:read scope. Returns at most 100 items per call, always tenant-scoped to the key that made the request.
Verify signatures#
Every delivery — REST hook push or test-fire — carries an X-VeloCMS-Signature header. Verify it against the secret returned once when you created the subscription, before trusting the payload.
import { createHmac, timingSafeEqual } from "node:crypto";
function verifyVeloCmsSignature(
rawBody: string,
timestamp: string,
signatureHeader: string, // "v1=<hex>"
secret: string
): boolean {
const expected = createHmac("sha256", secret)
.update(`v1:${timestamp}:${rawBody}`)
.digest("hex");
const received = signatureHeader.replace(/^v1=/, "");
if (expected.length !== received.length) return false;
return timingSafeEqual(Buffer.from(expected), Buffer.from(received));
}If you are wiring VeloCMS via Zapier's or Make's own built-in webhook trigger (rather than a published VeloCMS app that verifies this for you), your receiving endpoint is responsible for this check — a Google Apps Script "Code by Zapier" step or a Make "Custom JS" module both work well for this.
Zapier setup (today, no publication required)#
Search Zapier's app directory for Webhooks by Zapier — this is Zapier's own built-in connector, not a VeloCMS app. Choose Retrieve Poll or Catch Hook as the trigger event depending on whether you want the polling or push model above, point it at the matching VeloCMS endpoint with your velo_... API key as a Bearer token, and every event in the catalog above is available. A first-party, branded "VeloCMS" Zapier app — with a real event dropdown instead of a raw URL — is on the roadmap; this generic path already works end-to-end while that submission is pending.
Make setup (self-service, zero backend work)#
Make's generic HTTP / Make a request module (or the native Webhooks module for the push model) accepts a static Bearer token header with no app registration at all — this is the fastest path to a working scenario. Add a Webhooks module, copy its generated URL, and register it against VeloCMS the same way the curl example above does. From /admin/settings/api-keys you can also copy a ready-made subscribe command with your own blog's base URL pre-filled in.
Scopes & rate limits#
| Scope | Grants |
|---|---|
| webhooks:read | List subscriptions, poll the events endpoint |
| webhooks:write | Create, update, delete, and test-fire subscriptions |
| posts:write | Create draft posts (Action: create post) |
| members:write | Create or update members (Action: create/update member) |
Requests are rate-limited per API key on both a per-minute and per-hour window, scaled by your plan (Pro, Business, Agency). All Zapier/Make trigger and Action calls share the same limiter every other /api/v1/* endpoint uses — see the X-RateLimit-* response headers on any call. Webhook subscriptions require a Pro plan or higher.