Triggers
Three ways a flow can start.
API trigger
The simplest. Your service explicitly calls execute:
/api/flows/{id}/execute{ "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.
/api/triggers/wh/{token}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.
| Example | Meaning |
|---|---|
*/15 * * * * | Every 15 minutes |
0 9 * * * | Every day at 09:00 |
0 9 * * 1-5 | Weekdays at 09:00 |
0 0 1 * * | First of every month at 00:00 |
0 17 * * 5 | Fridays 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
/api/cron-previewcurl ".../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:
| Direction | Header sender | Secret used |
|---|---|---|
| Inbound trigger | Your partner | The flow's webhookSecret (rotatable per-flow). |
| Outbound event | Quantum AI | The 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.