Webhooks
Webhooks let systems outside PrimeCal trigger your automation rules automatically. When an external service — a booking platform, a CRM, a web store — posts a JSON message to your rule's webhook URL, PrimeCal receives it and runs the rule.
How Webhooks Work
Every automation rule that uses the webhook.incoming trigger gets its own unique URL. When a third-party system sends a signed HTTP POST request to that URL with a JSON body, your rule fires.
The flow is:
- External system sends a POST request to your webhook URL
- PrimeCal receives the payload and logs it
- The rule's conditions are evaluated against the payload data
- If conditions match (or there are no conditions), the action runs
Finding Your Webhook URL
- Go to Dashboard → Automation
- Open the rule you want to connect (or create a new one with
webhook.incomingas the trigger) - In the rule detail view, open the Webhook tab
- Copy the URL — it looks like:
https://api.primecal.eu/api/automation/webhook/abc123xyz
Each rule has a different token in the URL. This token identifies your rule, but on its own it is not enough to trigger it — see below.
Unlike many simple webhook receivers, PrimeCal requires every incoming request to carry a valid HMAC-SHA256 signature, not just the URL token. A request without one is rejected. This is more setup than a bare POST, but it means the token leaking on its own can't be used to trigger your rule.
Sending a Webhook Request
Your external system needs to send a request with Content-Type: application/json and two
required headers: a timestamp and a signature computed from your payload and a per-rule secret.
Example payload:
{
"event": "order.completed",
"data": {
"order_id": "12345",
"customer": "John Doe",
"total": 99.99,
"status": "paid"
}
}
PrimeCal accepts any valid JSON structure. The top-level shape is flexible — you can use data, payload, body, or any nesting you prefer. What matters is that you reference the same paths in your conditions using dot notation (e.g., webhook.data.order_id).
Getting your signing secret
The web app doesn't currently show your rule's signing secret directly. Retrieve (or rotate) it by calling, while signed in:
curl -X POST https://api.primecal.eu/api/automation/rules/YOUR_RULE_ID/webhook/rotate-secret \
-H "Authorization: Bearer YOUR_JWT_OR_API_KEY"
This returns a webhookSecret — save it somewhere safe, since there's no way to view it again
without rotating it (which invalidates the old one after a short grace period). Most integrators
do this once, then hard-code the secret in the sending system.
Computing the signature
For each request:
timestamp= current Unix time in seconds.- Build the string
"{timestamp}.{rawJsonBody}". signature=HMAC-SHA256(webhookSecret, thatString), as hex.- Send
X-PrimeCal-Timestamp: {timestamp}andX-PrimeCal-Signature: {signature}.
The timestamp must be within 5 minutes of PrimeCal's clock, and each signed request can only be used once — this stops old captured requests from being replayed later.
Full request/response examples (Node.js and curl) are in the Webhook API reference — that's the page to hand to whoever is implementing the sending side.
Testing with curl
Because of the required signature, testing needs the secret and a computed signature, not just a raw POST. The fastest way to test is to generate the signature inline:
SECRET="the webhookSecret you got from rotate-secret"
BODY='{"event":"test","data":{"priority":"high","customer_id":"123"}}'
TS=$(date +%s)
SIG=$(printf '%s.%s' "$TS" "$BODY" | openssl dgst -sha256 -hmac "$SECRET" | sed 's/^.* //')
curl -X POST https://api.primecal.eu/api/automation/webhook/YOUR_TOKEN \
-H "Content-Type: application/json" \
-H "X-PrimeCal-Timestamp: $TS" \
-H "X-PrimeCal-Signature: $SIG" \
-d "$BODY"
Replace YOUR_TOKEN with the token from your rule's Webhook tab. After sending, check the rule's History tab in PrimeCal to confirm the request was received and logged.
Using Payload Data in Conditions
Once a webhook arrives, you can inspect its contents inside conditions using webhook.data with dot notation:
webhook.data.order_id→ theorder_idfield underdatawebhook.data.customer→ thecustomerfield underdatawebhook.data.status→ thestatusfield underdata
Example rule: Only act when status equals "paid":
- Condition field:
webhook.data.status - Operator:
equals - Value:
paid
See Smart Values for a full explanation of dot notation and nested access.
Practical Example: Booking Confirmation
A reservation platform sends a webhook to PrimeCal when someone books a pool session. The payload looks like:
{
"event": "booking.confirmed",
"data": {
"booking_id": "bk_001",
"guest_name": "Sarah Connor",
"slot": "2026-07-15T10:00:00",
"type": "pool"
}
}
You want to color-code all incoming pool bookings in cyan so they stand out on the calendar.
Rule setup:
- Trigger:
webhook.incoming - Condition:
webhook.data.typeequalspool - Action: Set event color → Cyan
When the reservation system fires the webhook, PrimeCal receives it, checks that type is pool, and applies the cyan color to the associated event.
Regenerating the URL Token vs. Rotating the Signing Secret
These are two different values, and PrimeCal has two different operations for them:
- The URL token (the part in the webhook URL) — regenerate it from the rule's Webhook tab if you suspect the URL itself has leaked. The old URL stops working immediately.
- The signing secret — rotate it via the API (see above) if you suspect the secret has leaked, or periodically as a precaution. The old secret keeps working for a short grace period so you can update the sending system without an outage, then stops.
Regenerating one does not affect the other — rotate whichever was actually exposed.
Checking Webhook Logs
Every webhook request — whether it triggered the action or was filtered out by conditions — is logged in the rule's audit trail.
- Open the rule detail view
- Go to the History tab
- Each entry shows the timestamp, the incoming payload, which conditions were evaluated, and the outcome
This is the first place to check if a webhook doesn't seem to be working.
Troubleshooting
If your webhook isn't triggering the rule as expected, see Troubleshooting Automations for a full checklist. The most common issues are:
- 401 Unauthorized — missing or invalid
X-PrimeCal-Signature/X-PrimeCal-Timestampheaders, a timestamp more than 5 minutes off, or a signature computed against the wrong secret or the wrong raw body. This never reaches the History tab, since the request is rejected before the rule runs — check your sending system's own logs for the HTTP response. - Wrong
Content-Typeheader (must beapplication/json) - Token in the URL doesn't match the current rule token
- A condition is filtering out the request (check the History tab for the reason — a 200 response means the request was accepted and evaluated, not that it matched)