Webhook triggers
A webhook lets an external service post a signed payload and have CommandLatch fire a pinned action on the paired device.
What it is
Section titled “What it is”Each webhook is a narrow, single-purpose credential:
- One device (the action fires there and only there).
- One action (
lock,lock_sleep,keep_awake_start, etc.). - One signing secret (HMAC-SHA256 over the request body).
- One rate limit (per minute, sliding window).
- Revocable independently from the device’s pairing.
The request body is optional. When supplied, its payload field merges
into the webhook’s stored payload defaults, so callers can pass per-event
fields like delay_secs without rewriting the webhook config.
1. Create a webhook
Section titled “1. Create a webhook”In the web dashboard, expand Webhooks under the target device, fill in a name + action, and submit. The page shows the webhook URL, the signing secret, and a ready-to-use curl example.
Copy both the URL and the secret right away — the secret is shown exactly once. If you lose it, delete the webhook and create a new one.
2. Fire the webhook
Section titled “2. Fire the webhook”Stripe-style signature header: X-Signature: t=<unix_seconds>,v1=<hex_hmac>.
HMAC input is <timestamp> "." <raw_body>, key is the signing secret.
SECRET='<paste the signing secret>'URL='<paste the webhook URL>'
TS=$(date +%s)BODY='{"payload":{"delay_secs":60}}' # optional; '' is fine too# -binary | od ... reads the raw HMAC bytes and hex-encodes them, which# avoids relying on openssl's text output format (it differs between# LibreSSL and OpenSSL 3.x — see Troubleshooting below).SIG=$(printf "%s.%s" "$TS" "$BODY" | openssl dgst -sha256 -hmac "$SECRET" -binary | od -An -tx1 | tr -d ' \n')
curl -X POST "$URL" \ -H "X-Signature: t=$TS,v1=$SIG" \ -H "Content-Type: application/json" \ --data-raw "$BODY"Successful trigger:
{ "command_id": "…", "expires_at": "2026-05-14T…Z" }The Mac picks up the command within a couple of seconds.
Signature scheme
Section titled “Signature scheme”header: X-Signature: t=<unix_seconds>,v1=<hex_hmac_sha256>input: "<unix_seconds>.<raw_request_body_bytes>"key: webhook signing secret (32 random bytes, base64-encoded)digest: HMAC-SHA256, hex-encoded lowercaseThe server rejects anything outside ±5 minutes of its own clock to bound
replay windows. Empty bodies are allowed — the HMAC then signs
"<ts>.", which still proves possession of the secret.
The signing secret is used only for HMAC verification and is never returned through the API — not even to you.
Rate limiting
Section titled “Rate limiting”Each webhook is limited to 12 requests per minute by default. Every request counts — including rejected ones — so if a caller hits the limit, it stays limited until the window resets.
Contact support if a legitimate automation needs a higher limit.
Response codes
Section titled “Response codes”| Status | Body | Meaning |
|---|---|---|
201 | {"command_id","expires_at"} | Queued for the agent. |
401 | {"error":"invalid_webhook"} | Unknown id or no signature. |
401 | {"error":"bad_signature"} | HMAC mismatch. |
401 | {"error":"stale_timestamp"} | Skew > 5 minutes. |
403 | {"error":"webhook_disabled"} | Owner toggled it off. |
403 | {"error":"remote_disabled"} | Device has remote commands off. |
429 | {"error":"rate_limited"} | Window cap exceeded. |
405 | {"error":"method_not_allowed"} | Use POST (or OPTIONS for CORS). |
500 | {"error":"…"} | Database error; retry. |
Worked example: Zapier “When task is done, lock my Mac”
Section titled “Worked example: Zapier “When task is done, lock my Mac””- Create a webhook on the dashboard with action
lock_sleepand default payload{"delay_secs": 300}. - Copy the URL + secret.
- In Zapier, add a Code step that computes the signature (the
snippet above maps directly), then a Webhooks → POST step that
sends the body with
X-Signatureset from the previous step’s output. - The next time Zapier fires the trigger, the Mac displays a banner
(if the webhook’s action were
send_notification) or starts a 5-minute pending lock. Cancel from the menu bar, the dashboard, orcommandlatch done --cancel.
Troubleshooting
Section titled “Troubleshooting”{"error":"bad_signature"}with the correct secret. The HMAC input must be exactly<timestamp>.<raw_body>— no trailing newline, no re-serialized JSON. If your tool appends a newline (e.g.echowithout-n), the digest won’t match.{"error":"bad_signature"}usingopenssl dgstwithawk. The output format ofopenssl dgst -hmacdiffers between LibreSSL and OpenSSL 3.x. The example above avoids this by piping through-binary | od -An -tx1 | tr -d ' \n', which produces consistent output on both. If you’re adapting another snippet, confirm yourSIGvariable contains a 64-character hex string.{"error":"stale_timestamp"}. The request timestamp is more than 5 minutes from the server’s clock. Ensure your system clock is synchronized (NTP), or compute the timestamp immediately before sending the request.- Empty body but signature mismatch. Use
BODY=''explicitly. IfBODYis unset,printfmay produce an unexpected result. The HMAC input and the request body must always match exactly. - Lost signing secret. The secret is shown only once in the dashboard and cannot be retrieved later. If you lose it, delete the webhook and create a new one.
- Webhook fires the wrong action. The action is set at creation and cannot be changed. Delete the webhook and create a new one — the URL will change, which ensures any upstream callers are updated deliberately.
See also
Section titled “See also”- Shortcut endpoints — unsigned per-action URLs for phones and no-code tools.
- The
commandlatchCLI — drive the same actions from a terminal or CI. - Security & trust model — how webhook tokens are scoped and revoked.