Connect Zapier to Atlas Labs
To connect Zapier to Atlas Labs, use Webhooks by Zapier: a Catch Raw Hook trigger receives events at a hooks.zapier.com URL that you subscribe with POST /webhooks, and a Custom Request action calls the REST API with an Authorization: Bearer header. Zapier does not verify the Standard Webhooks signature for you, so this page includes the Code by Zapier step that does.
| At a glance | Value |
|---|---|
| Receive events | Webhooks by Zapier → Catch Raw Hook (keeps headers and the raw body; Catch Hook parses the body and drops the headers) |
| Call the API | Webhooks by Zapier → Custom Request; or the premium beta app API by Zapier → API Request |
| Where the key goes | Headers section: Authorization = Bearer lab_off_YOUR_KEY (Head Office) or Bearer lab_fac_YOUR_KEY (Factory) |
| Body | Data field, raw JSON, with header Content-Type: application/json |
Idempotency-Key | The webhook-id header from the Catch Raw Hook (it equals the event id, evt_…), or the id that the Code by Zapier step outputs after parsing the raw body |
| Webhook host | hooks.zapier.com is a recognised platform host with a higher delivery allowance |
| Plan | Webhooks by Zapier is on Professional, Team and Enterprise, not Free; API by Zapier is premium and beta (Zapier help) |
| OpenAPI import | None found in Zapier's documentation; use the HTTP steps |
lab_off_YOUR_KEY and lab_fac_YOUR_KEY. After you start a sandbox, the “Connect it” panel's Zapier tab shows these field values with your real keys and a copy button. POST, event JSON
Bearer key
How do I receive Atlas Labs events in a Zap?
- Create a Zap → Trigger: Webhooks by Zapier → event Catch Raw Hook → Continue. It hands later steps exactly two things, the unparsed body and the headers, so fields such as
idanddata.sales_order_idappear only after the Code step in the next section parses the body. Pick Catch Hook instead only if you will not verify the signature: it parses the body for you but discards the headers and the raw bytes. - Copy the webhook URL Zapier shows, of the form
https://hooks.zapier.com/hooks/catch/…/…/. - Subscribe that URL in Atlas Labs with either key. Save the
whsec_secret from the response; it is shown once.- Method
POST- URL
https://automationatlas.io/labs/api/v1/webhooks- Headers
Authorization: Bearer lab_off_YOUR_KEYContent-Type: application/json- Body
{"url": "https://hooks.zapier.com/hooks/catch/…/…/", "events": ["office.sales_order.accepted"], "description": "accepted orders"}
Subscribe to
office.sales_order.acceptedwhen the Zap will bridge the order into a work order, as in the next section:POST /factory/work-ordersaccepts only an order whose status isacceptedand answers409 invalid_transitionfor any other.office.sales_order.receivedfires earlier, when the order arrives, and suits a listen-only Zap or the ping demo. - Send the test ping (
POST /webhooks/{id}/test, in the snippet below), then click Test trigger in Zapier. The subscription turnsactiveonce Zapier answers2xx; Zapier's test only shows the3most recent webhooks from the past hour. - Publish the Zap. Atlas Labs re-delivers a failed event after
10 s,60 sand300 s, then disables the subscription after5consecutive failures.
snippets/06-subscribe-webhook.sh bash, curl, jq proven against the live API (create, replay, test ping) #!/usr/bin/env bash
# Subscribe a public HTTPS URL (port 443) to events, keep the secret, send the test ping.
# Up to 3 subscriptions per sandbox. Needs WEBHOOK_URL, for example a Zapier Catch Hook URL.
set -euo pipefail
BASE="${ATLAS_BASE:-https://automationatlas.io/labs/api/v1}"
: "${ATLAS_OFFICE_KEY:?run: source atlas-keys.env}"
: "${WEBHOOK_URL:?set WEBHOOK_URL to your public https:// endpoint}"
created=$(curl -sS -X POST "$BASE/webhooks" \
-H "Authorization: Bearer $ATLAS_OFFICE_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: subscribe-orders-1" \
-d "$(jq -n --arg url "$WEBHOOK_URL" '{url: $url, events: ["office.sales_order.received"], description: "new orders"}')")
echo "$created" | jq '.data | {id, status, url_host, events}'
# The whsec_ secret is returned once. Replaying the same Idempotency-Key never repeats it (secret is null).
secret=$(jq -r '.data.secret // empty' <<<"$created")
[ -n "$secret" ] && (umask 077; printf '%s' "$secret" > atlas-webhook-secret.txt; chmod 600 atlas-webhook-secret.txt) || echo "no secret in this response (a replay?)" >&2
ID=$(jq -r '.data.id' <<<"$created")
# The subscription starts as pending_verification and activates when this ping gets a 2xx.
curl -sS -X POST "$BASE/webhooks/$ID/test" -H "Authorization: Bearer $ATLAS_OFFICE_KEY" | jq .data
curl -sS "$BASE/webhooks/$ID" -H "Authorization: Bearer $ATLAS_OFFICE_KEY" | jq '.data | {status, consecutive_failures, last_error_class}'
The tested file is shown unchanged and subscribes to office.sales_order.received, the listen-only case. For the bridge Zap below, set the events value to office.sales_order.accepted as in the field box above.
Does Zapier verify the signature?
No. Add Code by Zapier (Run JavaScript) right after the Catch Raw Hook and map the raw body and the webhook-id, webhook-timestamp and webhook-signature headers into its input data. The step does two jobs. First, paste the verify function below, leaving out its two import lines and the export keyword, replace process.env.ATLAS_WEBHOOK_SECRET with the secret passed in as an input field of the step (never a whsec_ value pasted into the code), and throw when it returns false, so the Zap stops. The excerpt is the tested Node file; how node:crypto is loaded inside Code by Zapier is not something we have confirmed, so check Zapier's Code docs for the form your step accepts. Second, parse the body with JSON.parse and return the fields later steps will map, for example id, type and data.sales_order_id; without this step a Catch Raw Hook offers no id or sales_order_id fields, only the raw body and the headers. The function needs the raw bytes, which is why the trigger must be Catch Raw Hook.
snippets/07-verify-webhook.mjs excerpt: the header comment and the verify function, as in the tested file; the server part is cut where // … stands verified against the repo's own signer: good, rotated, tampered, wrong-secret, stale // Receive and verify an Atlas Labs webhook (Node 20+, no dependencies).
// Standard Webhooks: headers webhook-id, webhook-timestamp, webhook-signature.
// signature = "v1," + base64(HMAC-SHA256(key, `${id}.${timestamp}.${rawBody}`)), where key is
// the base64-decoded part of your whsec_ secret. During the 24 h after rotate-secret the header
// holds two space-separated values ("v1,<new> v1,<old>"); accept either.
import { createHmac, timingSafeEqual } from "node:crypto";
import { createServer } from "node:http";
const SECRET = process.env.ATLAS_WEBHOOK_SECRET; // whsec_...
const TOLERANCE_S = 300;
export function verify(secret, headers, rawBody, nowS = Math.floor(Date.now() / 1000)) {
const id = headers["webhook-id"];
const timestamp = headers["webhook-timestamp"];
const header = headers["webhook-signature"];
if (!id || !timestamp || !header) return false;
if (Math.abs(nowS - Number(timestamp)) > TOLERANCE_S) return false; // stale or replayed
const key = Buffer.from(secret.replace(/^whsec_/, ""), "base64");
const expected = createHmac("sha256", key).update(`${id}.${timestamp}.${rawBody}`, "utf8").digest();
return header.split(" ").some((part) => {
const [version, value] = part.split(",");
if (version !== "v1" || !value) return false;
const given = Buffer.from(value, "base64");
return given.length === expected.length && timingSafeEqual(given, expected);
});
}
// … How do I call the Atlas Labs API from a Zap?
- Add an action → Webhooks by Zapier → event Custom Request. Use Custom Request rather than the plain POST event so the JSON in the Data field is sent exactly as typed.
- Fill the request. This example bridges an accepted sales order into a Factory work order, so it uses the Factory key. Look the order up first with a Custom Request
GET https://automationatlas.io/labs/api/v1/office/sales-orders/<id>(Head Office key), with<id>mapped from the Code step'ssales_order_idoutput, to getquantityanddue_sim_day.- Method
POST- URL
https://automationatlas.io/labs/api/v1/factory/work-orders- Headers
Authorization: Bearer lab_fac_YOUR_KEYContent-Type: application/jsonIdempotency-Key:insert thewebhook-idheader from the trigger (it equals the eventid), or the Code step'sidoutput- Data (raw JSON)
{"sales_order_id": "<sales_order_id from the Code step>", "quantity": <quantity from the order>, "due_date": <due_sim_day from the order>}- Response fields
- After a test, Zapier offers the step's HTTP status field and the response body to later steps. Their exact names are not given in the help page we read (unverified), so pick them from the list Zapier shows.
Preset matters. Pick a preset with a manual bridge (Standard or Full) or this write returns
409 already_bridged, because Starter bridges automatically. - Treat
409 already_bridgedas success in a Filter or Paths step: continue when the step's HTTP status field is201, or is409and the response body'serror.codeisalready_bridged(a second delivery of the same event, or an order the sandbox bridged itself). Send yourself anything else, including409 invalid_transition: that one is a real error and means the order's status is notaccepted, usually because the Zap was triggered byoffice.sales_order.receivedinstead ofoffice.sales_order.accepted. - Prefer to keep the key out of the Zap? Apps → Add connection → API by Zapier → authentication type Static Headers (API key) → one header per line:
Authorization: Bearer lab_fac_YOUR_KEY. Then use its API Request action; Zapier adds the header itself. API by Zapier is premium and in beta.
Idempotency and retries
- Use the event
idas theIdempotency-Key: thewebhook-idheader from the Catch Raw Hook, or theidthe Code step returns after parsing. A replayed Zap run then returns the original Atlas Labs response (Idempotency-Replayed: true) instead of creating a second record. - On
429 rate_limited, wait the seconds inRetry-Afterand send the same request. Zapier's own retry behaviour for Webhooks by Zapier is not described in the help pages we read, so do not rely on it. - Leaving Data blank in a Custom Request sends every field from previous steps as the body; always type the JSON.
Common errors
| You see | Why, and the fix |
|---|---|
403 wrong_system | The key prefix does not match the route. /factory/* wants lab_fac_, /office/* wants lab_off_; the hint says which. |
409 already_bridged | Not an error for this Zap: the order already has a work order (a second delivery of the same event, or a Starter sandbox bridged it itself). Treat it as success, as the Filter step above does. |
409 invalid_transition | “only accepted sales orders can be bridged”: the order's status is not accepted. The Zap fired on office.sales_order.received, or the order was never accepted. Subscribe to office.sales_order.accepted, or accept it first with POST/office/sales-orders/{id}/accept. |
401 unauthorized | The key is unknown (a typo), revoked (for example after it appeared in a URL) or deleted with its sandbox. An expired sandbox is different: its writes return 423 sandbox_expired. Start a new sandbox and update the header. |
415 unsupported_media_type | The body arrived without Content-Type: application/json. Add the header (see the gotcha below). |
422 idempotency_key_reused | Same key, different body or path. Map a value that is stable per event, never one that changes between runs. |
422 webhook_url_rejected | The URL was not public https:// on port 443; details.reason names the rule. Paste the full Zapier URL including https://. |
| Test trigger finds nothing | Send the test ping and click Test trigger within the hour; only the 3 newest requests are offered. |
Gotchas
- Plan gating. Webhooks by Zapier (send and catch) is not available on the Free plan; API by Zapier is a premium, beta app (Zapier help centre, checked
15 Oct 2026). - Raw body, no fields. Signature verification needs the exact bytes. Catch Raw Hook keeps them (up to
2 MB) and includes the headers, but offers nothing parsed: mapid,typeanddata.sales_order_idfrom the Code step's output, never from the trigger. Catch Hook is the reverse: parsed fields, no raw bytes, no headers. - Content-Type. Zapier's help says not to set a custom
Content-Typeon Webhooks by Zapier, while a community thread reports that addingapplication/jsonfixed a failing request. Test your Zap once with the header; Atlas Labs requires it on writes. - Per-app API Request is not for Atlas Labs. The built-in “API Request” action inside other apps only reaches apps that already have a Zapier integration. Use Webhooks by Zapier or API by Zapier.
- Underscored headers. A community thread reports Catch Raw Hook dropping headers whose names contain an underscore (unverified). Atlas Labs headers use hyphens, so this should not affect you.
Download the recipe
POST /factory/work-orders → Filter on 201 or 409 already_bridged. Needs a manual-bridge preset (Standard or Full).Questions people ask
- Will Atlas Labs deliver webhooks to a Zapier URL?
- Yes.
hooks.zapier.comis public HTTPS on port443, which is all a subscription needs, and it is one of the shared platform hosts that get a higher delivery allowance. - Does Zapier verify the Standard Webhooks signature?
- No. Use Catch Raw Hook and the Code by Zapier step above. For a fictional sandbox you may decide to skip it; the payload is only ever simulation data.
- Which Zapier plan do I need?
- Webhooks by Zapier is on Professional, Team and Enterprise (Zapier help centre, checked
15 Oct 2026). Without it, use Make, n8n or the REST snippets. - Can Zapier import the Atlas Labs OpenAPI file?
- We found no OpenAPI or Swagger import in Zapier's documentation. Build the two HTTP steps by hand; it takes under a minute.
- What should I use as the Idempotency-Key in a Zap?
- The event id (
evt_…): thewebhook-idheader on a Catch Raw Hook, or the parsedidfield on a Catch Hook. Never a timestamp or a random value. - Why can't I find
idorsales_order_idin the Catch Raw Hook's fields? - Because Catch Raw Hook keeps the body unparsed. Either parse it in the Code by Zapier step and map that step's outputs, or switch the trigger to Catch Hook, which parses the body but drops the headers, so the signature cannot be checked.
- Why does my Zap get
403 wrong_system? - You sent the Head Office key to a Factory route, or the other way round. Both keys appear once, when the sandbox is created; keep them labelled.
Sources (checked 15 Oct 2026): Zapier help centre, “Send webhooks in Zaps” (8496326446989), “Trigger Zaps from webhooks” (8496288690317), “Send API requests in Zap workflows” (44391650357005), “How to get started with API by Zapier” (44391660158733). Atlas Labs facts from the API source, observed live.