Skip to main content
Was this helpful?

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:

  1. External system sends a POST request to your webhook URL
  2. PrimeCal receives the payload and logs it
  3. The rule's conditions are evaluated against the payload data
  4. If conditions match (or there are no conditions), the action runs

Finding Your Webhook URL​

  1. Go to Dashboard → Automation
  2. Open the rule you want to connect (or create a new one with webhook.incoming as the trigger)
  3. In the rule detail view, open the Webhook tab
  4. 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.

Requests must be signed — a plain POST will fail

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:

  1. timestamp = current Unix time in seconds.
  2. Build the string "{timestamp}.{rawJsonBody}".
  3. signature = HMAC-SHA256(webhookSecret, thatString), as hex.
  4. Send X-PrimeCal-Timestamp: {timestamp} and X-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 → the order_id field under data
  • webhook.data.customer → the customer field under data
  • webhook.data.status → the status field under data

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:

  1. Trigger: webhook.incoming
  2. Condition: webhook.data.type equals pool
  3. 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.

  1. Open the rule detail view
  2. Go to the History tab
  3. 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-Timestamp headers, 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-Type header (must be application/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)