📘 Public beta · Endpoints are stable; OpenAPI specs and SDKs ship monthly. See changelog →
Products
AI Automation
Triggers (API · webhook · cron)

Triggers

Three ways a flow can start.

API trigger

The simplest. Your service explicitly calls execute:

POST/api/flows/{id}/execute
Auth · API keyScope · runs:write
{ "ticketId": "...", "subject": "...", "body": "..." }

Use when:

  • Your code already has the data and just needs the flow to run.
  • You want full control over when and what triggers.
  • The trigger is internal (no untrusted external caller).

Webhook trigger

A public URL that fires a flow when posted to. HMAC-signature-protected, no API key required.

POST/api/triggers/wh/{token}
Auth · HMAC-signed

URL format:

https://api.quantumelixir.tech/ai-automation/api/triggers/wh/wht_abcd1234...

The token lives in the flow's webhookToken field. Treat it like a credential — anyone with the token can attempt to trigger; only signed-body submissions succeed.

Signature header

POST /api/triggers/wh/wht_... HTTP/1.1
Content-Type:        application/json
X-Quantum-Signature: sha256=<hex of HMAC-SHA256(webhookSecret, raw_body)>

{ "ticketId": "...", "subject": "...", "body": "..." }

Signing on the sender side

import crypto from 'crypto';

const body = JSON.stringify({ ticketId, subject, body: text });
const sig = 'sha256=' + crypto
  .createHmac('sha256', process.env.QE_WEBHOOK_SECRET!)
  .update(body, 'utf8')
  .digest('hex');

await fetch('https://api.quantumelixir.tech/ai-automation/api/triggers/wh/wht_...', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-Quantum-Signature': sig,
  },
  body,
});

Use when:

  • A partner's system needs to fire the flow (you give them token + secret).
  • The trigger is external (always HMAC-protected; API key would be too broad).

Grace window on secret rotation

When you POST /api/flows/{id}/rotate-webhook-secret, the old secret stays valid for 24 hours. This lets you deploy the new secret to senders without dropping in-flight signed requests.

After 24h, only the new secret works. We don't notify on grace expiry — the dashboard surfaces it visually if you check during the window.

Cron trigger

For scheduled flows (every 15 min, every night at 9pm, every weekday morning).

{
  "triggerType": "cron",
  "cronExpression": "*/15 * * * *",
  "cronEnabled": true
}

Expression syntax

Standard 5-field cron: minute hour day-of-month month day-of-week.

ExampleMeaning
*/15 * * * *Every 15 minutes
0 9 * * *Every day at 09:00
0 9 * * 1-5Weekdays at 09:00
0 0 1 * *First of every month at 00:00
0 17 * * 5Fridays at 17:00

Timezone

Cron expressions are evaluated in your org's configured timezone (set in the dashboard's General Settings page). Default: Asia/Jakarta.

Tick resolution

The cron scheduler ticks every 60 seconds. A */15 expression fires every 15 minutes ± 60 seconds. For sub-minute precision (rare), use an external scheduler that calls the API trigger.

Preview

GET/api/cron-preview
Auth · API keyScope · flows:read
curl ".../api/cron-preview?expression=0+9+*+*+1-5&count=5" \
  -H "Authorization: Bearer $QE_API_KEY"

Returns the next 5 fire times. Useful for verifying your cron expression before saving.

Pausing cron without deleting

Set cronEnabled: false. The expression stays; we just stop firing. Re-enable later by flipping the flag.

curl -X PATCH .../api/flows/ticket-triage-v1 \
  -d '{ "cronEnabled": false }'

Switching trigger types

Any flow can switch trigger type via PATCH — your webhookToken is preserved across switches, so a flow that used to be API-triggered keeps its (unused) webhook URL. Useful when you start API-only and add webhook triggers later without renumbering.

curl -X PATCH .../api/flows/ticket-triage-v1 \
  -d '{ "triggerType": "webhook" }'

Webhook triggers don't have caller auth — body shape matters

Anyone with the token + secret can POST. Your flow's first node should be defensive: validate the body against an expected schema and fail loudly on missing fields. Don't assume the body is what you expected.

{
  "id": "validate_body",
  "type": "transform",
  "expression": "{ ticketId: trigger.ticketId, subject: trigger.subject, body: trigger.body }",
  "schema": {
    "type": "object",
    "required": ["ticketId", "subject", "body"],
    "properties": {
      "ticketId": { "type": "string", "minLength": 1 },
      "subject":  { "type": "string", "maxLength": 500 },
      "body":     { "type": "string", "maxLength": 50000 }
    }
  },
  "onSchemaFail": "fail_run"
}

Same header, different secrets

Both incoming trigger webhooks (this page) and outgoing event webhooks (delivered by Webhooks →) use the header X-Quantum-Signature: sha256=<hex>. The secret material is not shared:

DirectionHeader senderSecret used
Inbound triggerYour partnerThe flow's webhookSecret (rotatable per-flow).
Outbound eventQuantum AIThe subscription's bearer secret (qe_whsec_..., rotatable per-subscription).

Never reuse webhookSecret as a verification secret on your outbound webhook receiver — they protect different directions and rotate independently.