A trigger starts an agent’s flow without a customer message. A webhook trigger gives you a URL that starts a run when something posts to it; a schedule starts runs on a cron or once at a set time. Triggered runs are headless: there is no contact, nothing is delivered to a channel, and the output is captured on the run.
Calling a webhook ​
POST your JSON to the trigger’s url. It answers 202 with { "runId": "…" }. Read the outcome back with GET {url}/runs/{runId} — the token in the URL is the credential for both, so neither call takes a bearer header. If signing is on, send the HMAC-SHA256 of the body in the trigger’s signatureHeader.
Endpoints
GET/bots/{botId}/triggersList webhook triggersPOST/bots/{botId}/triggers/webhookCreate a webhook triggerPATCH/bots/{botId}/triggers/webhook/{triggerId}Update a webhook triggerPOST/bots/{botId}/triggers/webhook/{triggerId}/rotateRotate a webhook triggerDELETE/bots/{botId}/triggers/webhook/{triggerId}Delete a webhook triggerGET/bots/{botId}/schedulesList schedulesPOST/bots/{botId}/schedulesCreate a schedulePATCH/bots/{botId}/schedules/{scheduleId}Update a scheduleDELETE/bots/{botId}/schedules/{scheduleId}Delete a scheduleThe webhook trigger object
Schedules are described under Create a schedule.
Attributes
idintegerUnique identifier of the webhook trigger.botIdintegerThe agent it starts.urlstringThe full public URL a sender posts to. It is built by the server — use it exactly as returned, and treat it as a credential: the token in the URL is what authorises a call.statusstringDISABLEDkeeps the URL but it answers404until you enable it again.One of:ENABLEDDISABLEDsigningEnabledbooleanWhether callers must sign the request body.signatureHeaderstringnullableThe header carrying the signature;nullmeans the default,X-Signature-SHA256.secretHintstringnullable********when signing is on, otherwisenull. The signing secret exists in plain text only in the response that created or rotated it.createdAtstringWhen it was created.updatedAtstringWhen it was last changed.
{
"id": 14,
"botId": 55,
"url": "https://api.cx.na6iq.com/webhooks/flows/x7Qp2mNf8Vb1LrTzK4cHaYe9",
"status": "ENABLED",
"signingEnabled": true,
"signatureHeader": null,
"secretHint": "********",
"createdAt": "2026-11-03T10:00:00Z",
"updatedAt": "2026-11-03T10:00:00Z"
}
List webhook triggers
Returns the agent’s webhook triggers as a plain array. The signing secret is never included.
builder.bots:readPath parameters
botIdintegerrequiredThe agent’sid.
Returns
200The webhook trigger object.
Errors
- 404
BOT_NOT_FOUNDNo agent with this ID in your organisation.
curl -X GET "https://api.cx.na6iq.com/api/v1/bots/42/triggers" \
-H "Authorization: Bearer $NATIQ_TOKEN"
const res = await fetch('https://api.cx.na6iq.com/api/v1/bots/42/triggers', {
method: 'GET',
headers: {
Authorization: `Bearer ${process.env.NATIQ_TOKEN}`,
},
})
if (!res.ok) throw new Error((await res.json().catch(() => ({}))).error?.message ?? `HTTP ${res.status}`)
const data = await res.json()
import os, requests
res = requests.get(
"https://api.cx.na6iq.com/api/v1/bots/42/triggers",
headers={"Authorization": f"Bearer {os.environ['NATIQ_TOKEN']}"},
)
res.raise_for_status()
data = res.json()
[
{
"id": 14,
"botId": 55,
"url": "https://api.cx.na6iq.com/webhooks/flows/x7Qp2mNf8Vb1LrTzK4cHaYe9",
"status": "ENABLED",
"signingEnabled": true,
"signatureHeader": null,
"secretHint": "********",
"createdAt": "2026-11-03T10:00:00Z",
"updatedAt": "2026-11-03T10:00:00Z"
}
]
Create a webhook trigger
Mints a delivery URL. With signing: true it also mints an HMAC signing secret. The response’s secret is the only time the plain-text secret exists — store it now; it is null when signing is off. The whole body is optional.
builder.bots:writePath parameters
botIdintegerrequiredThe agent’sid.
Request body
signingbooleanMint an HMAC signing secret alongside the URL.signatureHeaderstringA custom header name for the signature, up to 100 characters. Ignored unlesssigningistrue; can’t be changed later.
Returns
201The webhook trigger object.
Errors
- 404
BOT_NOT_FOUNDNo agent with this ID in your organisation.
curl -X POST "https://api.cx.na6iq.com/api/v1/bots/42/triggers/webhook" \
-H "Authorization: Bearer $NATIQ_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"signing": true
}'
const res = await fetch('https://api.cx.na6iq.com/api/v1/bots/42/triggers/webhook', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.NATIQ_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"signing": true
}),
})
if (!res.ok) throw new Error((await res.json().catch(() => ({}))).error?.message ?? `HTTP ${res.status}`)
const data = await res.json()
import os, requests
res = requests.post(
"https://api.cx.na6iq.com/api/v1/bots/42/triggers/webhook",
headers={"Authorization": f"Bearer {os.environ['NATIQ_TOKEN']}"},
json={
"signing": True
},
)
res.raise_for_status()
data = res.json()
{
"trigger": {
"id": 14,
"botId": 55,
"url": "https://api.cx.na6iq.com/webhooks/flows/x7Qp2mNf8Vb1LrTzK4cHaYe9",
"status": "ENABLED",
"signingEnabled": true,
"signatureHeader": null,
"secretHint": "********",
"createdAt": "2026-11-03T10:00:00Z",
"updatedAt": "2026-11-03T10:00:00Z"
},
"secret": "<SIGNING_SECRET>"
}
Update a webhook trigger
Enables or disables the trigger, or removes signing. signing only ever takes false: turning signing on mints a secret that must be handed over exactly once, so use Rotate a webhook trigger with rotateSecret: true for that — sending true is a 400. Removing signing keeps the URL. A disabled trigger stops answering within about a minute.
builder.bots:writePath parameters
botIdintegerrequiredThe agent’sid.triggerIdintegerrequiredThe webhook trigger’sid.
Request body
statusstringEnable or disable the URL.One of:ENABLEDDISABLEDsigningbooleanOnlyfalse, to remove signing.
Returns
200The webhook trigger object.
Errors
- 400
VALIDATION_FAILED`signing: true` isn’t supported here. Turn signing on by rotating with `rotateSecret: true`. - 400
INVALID_REQUESTUnknown `status`. - 404
WEBHOOK_TRIGGER_NOT_FOUNDNo such webhook trigger on this agent.
curl -X PATCH "https://api.cx.na6iq.com/api/v1/bots/42/triggers/webhook/42" \
-H "Authorization: Bearer $NATIQ_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"status": "DISABLED"
}'
const res = await fetch('https://api.cx.na6iq.com/api/v1/bots/42/triggers/webhook/42', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.NATIQ_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"status": "DISABLED"
}),
})
if (!res.ok) throw new Error((await res.json().catch(() => ({}))).error?.message ?? `HTTP ${res.status}`)
const data = await res.json()
import os, requests
res = requests.patch(
"https://api.cx.na6iq.com/api/v1/bots/42/triggers/webhook/42",
headers={"Authorization": f"Bearer {os.environ['NATIQ_TOKEN']}"},
json={
"status": "DISABLED"
},
)
res.raise_for_status()
data = res.json()
{
"id": 14,
"botId": 55,
"url": "https://api.cx.na6iq.com/webhooks/flows/x7Qp2mNf8Vb1LrTzK4cHaYe9",
"status": "DISABLED",
"signingEnabled": true,
"signatureHeader": null,
"secretHint": "********",
"createdAt": "2026-11-03T10:00:00Z",
"updatedAt": "2026-11-03T10:00:00Z"
}
Rotate a webhook trigger
Issues a new URL token — the old URL stops working within about a minute — and, with rotateSecret: true, a new signing secret too. They are separate flags because the two leak for different reasons. As on creation, the new secret is in the response only; secret is null unless rotateSecret is true. Sending rotateSecret: true on an unsigned trigger turns signing on.
builder.bots:writePath parameters
botIdintegerrequiredThe agent’sid.triggerIdintegerrequiredThe webhook trigger’sid.
Request body
rotateSecretbooleanAlso mint a new signing secret.
Returns
200The webhook trigger object.
Errors
- 404
WEBHOOK_TRIGGER_NOT_FOUNDNo such webhook trigger on this agent.
curl -X POST "https://api.cx.na6iq.com/api/v1/bots/42/triggers/webhook/42/rotate" \
-H "Authorization: Bearer $NATIQ_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"rotateSecret": true
}'
const res = await fetch('https://api.cx.na6iq.com/api/v1/bots/42/triggers/webhook/42/rotate', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.NATIQ_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"rotateSecret": true
}),
})
if (!res.ok) throw new Error((await res.json().catch(() => ({}))).error?.message ?? `HTTP ${res.status}`)
const data = await res.json()
import os, requests
res = requests.post(
"https://api.cx.na6iq.com/api/v1/bots/42/triggers/webhook/42/rotate",
headers={"Authorization": f"Bearer {os.environ['NATIQ_TOKEN']}"},
json={
"rotateSecret": True
},
)
res.raise_for_status()
data = res.json()
{
"trigger": {
"id": 14,
"botId": 55,
"url": "https://api.cx.na6iq.com/webhooks/flows/Zk3Wc9TqL2dEyU7pXnB5vRs1",
"status": "ENABLED",
"signingEnabled": true,
"signatureHeader": null,
"secretHint": "********",
"createdAt": "2026-11-03T10:00:00Z",
"updatedAt": "2026-11-03T10:00:00Z"
},
"secret": "<SIGNING_SECRET>"
}
Delete a webhook trigger
Deletes the trigger and its URL permanently; the URL stops working within about a minute. To pause one reversibly, set status to DISABLED instead.
builder.bots:writePath parameters
botIdintegerrequiredThe agent’sid.triggerIdintegerrequiredThe webhook trigger’sid.
Returns
204The trigger is deleted.
Errors
- 404
WEBHOOK_TRIGGER_NOT_FOUNDNo such webhook trigger on this agent.
curl -X DELETE "https://api.cx.na6iq.com/api/v1/bots/42/triggers/webhook/42" \
-H "Authorization: Bearer $NATIQ_TOKEN"
const res = await fetch('https://api.cx.na6iq.com/api/v1/bots/42/triggers/webhook/42', {
method: 'DELETE',
headers: {
Authorization: `Bearer ${process.env.NATIQ_TOKEN}`,
},
})
if (!res.ok) throw new Error((await res.json().catch(() => ({}))).error?.message ?? `HTTP ${res.status}`)
import os, requests
res = requests.delete(
"https://api.cx.na6iq.com/api/v1/bots/42/triggers/webhook/42",
headers={"Authorization": f"Bearer {os.environ['NATIQ_TOKEN']}"},
)
res.raise_for_status()
List schedules
Returns the agent’s schedules as a plain array, each with its upcoming fire times. An unknown agent answers 200 with an empty array.
builder.bots:readPath parameters
botIdintegerrequiredThe agent’sid.
Returns
200The webhook trigger object.
curl -X GET "https://api.cx.na6iq.com/api/v1/bots/42/schedules" \
-H "Authorization: Bearer $NATIQ_TOKEN"
const res = await fetch('https://api.cx.na6iq.com/api/v1/bots/42/schedules', {
method: 'GET',
headers: {
Authorization: `Bearer ${process.env.NATIQ_TOKEN}`,
},
})
if (!res.ok) throw new Error((await res.json().catch(() => ({}))).error?.message ?? `HTTP ${res.status}`)
const data = await res.json()
import os, requests
res = requests.get(
"https://api.cx.na6iq.com/api/v1/bots/42/schedules",
headers={"Authorization": f"Bearer {os.environ['NATIQ_TOKEN']}"},
)
res.raise_for_status()
data = res.json()
[
{
"id": 9,
"botId": 55,
"cron": "0 0 9 * * MON",
"timezone": "Asia/Amman",
"runAt": null,
"nextRunAt": "2026-11-09T06:00:00Z",
"status": "ENABLED",
"payload": {
"report": "weekly"
},
"nextFireTimes": [
"2026-11-09T06:00:00Z",
"2026-11-16T06:00:00Z",
"2026-11-23T06:00:00Z"
],
"createdAt": "2026-11-03T10:05:00Z",
"updatedAt": "2026-11-03T10:05:00Z"
}
]
Create a schedule
Puts the agent on a clock. Send exactly one of cron (repeating) or runAt (once). cron is a six-field Spring expression — second, minute, hour, day-of-month, month, day-of-week — evaluated in timezone. payload becomes the run’s trigger.payload. The response lists the next fire times so you can check the cron says what you meant. The agent must be ready to run first (published, with a trigger the flow accepts, and a complete payload); those checks run before the cron is checked. Scheduling needs its own permission, separate from editing the agent.
builder.bots:schedulePath parameters
botIdintegerrequiredThe agent’sid.
Request body
cronstringA six-field cron, e.g.0 0 9 * * MONfor 09:00 every Monday. Exclusive withrunAt.timezonestringIANA time zone the cron is evaluated in, e.g.Asia/Amman. Defaults toUTC.runAtstringOne-off fire time as an ISO 8601 instant. Must be in the future. Exclusive withcron.payloadobjectData handed to the run as its trigger payload.
Returns
201The webhook trigger object.
Errors
- 400
SCHEDULE_INVALIDBoth or neither of `cron` and `runAt`, a bad cron or time zone, or a `runAt` that is not in the future. - 409
FLOW_NOT_PUBLISHEDThe agent has no published flow. - 409
TRIGGER_TYPE_NOT_ACCEPTEDThe published flow does not accept schedule triggers. - 422
TRIGGER_PAYLOAD_INCOMPLETEThe `payload` is missing data the flow needs. - 404
BOT_NOT_FOUNDNo agent with this ID in your organisation.
curl -X POST "https://api.cx.na6iq.com/api/v1/bots/42/schedules" \
-H "Authorization: Bearer $NATIQ_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"cron": "0 0 9 * * MON",
"timezone": "Asia/Amman",
"payload": {
"report": "weekly"
}
}'
const res = await fetch('https://api.cx.na6iq.com/api/v1/bots/42/schedules', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.NATIQ_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"cron": "0 0 9 * * MON",
"timezone": "Asia/Amman",
"payload": {
"report": "weekly"
}
}),
})
if (!res.ok) throw new Error((await res.json().catch(() => ({}))).error?.message ?? `HTTP ${res.status}`)
const data = await res.json()
import os, requests
res = requests.post(
"https://api.cx.na6iq.com/api/v1/bots/42/schedules",
headers={"Authorization": f"Bearer {os.environ['NATIQ_TOKEN']}"},
json={
"cron": "0 0 9 * * MON",
"timezone": "Asia/Amman",
"payload": {
"report": "weekly"
}
},
)
res.raise_for_status()
data = res.json()
{
"id": 9,
"botId": 55,
"cron": "0 0 9 * * MON",
"timezone": "Asia/Amman",
"runAt": null,
"nextRunAt": "2026-11-09T06:00:00Z",
"status": "ENABLED",
"payload": {
"report": "weekly"
},
"nextFireTimes": [
"2026-11-09T06:00:00Z",
"2026-11-16T06:00:00Z",
"2026-11-23T06:00:00Z"
],
"createdAt": "2026-11-03T10:05:00Z",
"updatedAt": "2026-11-03T10:05:00Z"
}
Update a schedule
Changes when a schedule fires, or pauses and resumes it with status. Takes the same fields as Create a schedule, plus status. Sending cron switches the schedule to a cron schedule and clears runAt, and the reverse. payload replaces the whole payload. Resuming re-runs the readiness checks from Create a schedule. COMPLETED is set by the server on one-off schedules that already fired; sending it is a 400.
builder.bots:schedulePath parameters
botIdintegerrequiredThe agent’sid.scheduleIdintegerrequiredThe schedule’sid.
Request body
statusstringPause or resume the schedule.One of:ENABLEDPAUSEDcronstringA new six-field cron.timezonestringA new IANA time zone.runAtstringA new one-off time.payloadobjectA new trigger payload.
Returns
200The webhook trigger object.
Errors
- 400
SCHEDULE_INVALIDResuming a one-off schedule whose time has passed, or sending `COMPLETED` as `status`. - 400
INVALID_REQUESTUnknown `status`. - 409
FLOW_NOT_PUBLISHEDAs on create, when resuming. - 409
TRIGGER_TYPE_NOT_ACCEPTEDAs on create, when resuming. - 422
TRIGGER_PAYLOAD_INCOMPLETEAs on create, when resuming. - 404
SCHEDULE_NOT_FOUNDNo such schedule on this agent.
curl -X PATCH "https://api.cx.na6iq.com/api/v1/bots/42/schedules/42" \
-H "Authorization: Bearer $NATIQ_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"status": "PAUSED"
}'
const res = await fetch('https://api.cx.na6iq.com/api/v1/bots/42/schedules/42', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.NATIQ_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"status": "PAUSED"
}),
})
if (!res.ok) throw new Error((await res.json().catch(() => ({}))).error?.message ?? `HTTP ${res.status}`)
const data = await res.json()
import os, requests
res = requests.patch(
"https://api.cx.na6iq.com/api/v1/bots/42/schedules/42",
headers={"Authorization": f"Bearer {os.environ['NATIQ_TOKEN']}"},
json={
"status": "PAUSED"
},
)
res.raise_for_status()
data = res.json()
{
"id": 9,
"botId": 55,
"cron": "0 0 9 * * MON",
"timezone": "Asia/Amman",
"runAt": null,
"nextRunAt": "2026-11-09T06:00:00Z",
"status": "PAUSED",
"payload": {
"report": "weekly"
},
"nextFireTimes": [
"2026-11-09T06:00:00Z",
"2026-11-16T06:00:00Z",
"2026-11-23T06:00:00Z"
],
"createdAt": "2026-11-03T10:05:00Z",
"updatedAt": "2026-11-03T10:05:00Z"
}
Delete a schedule
Deletes the schedule permanently. To stop it reversibly, set its status to PAUSED.
builder.bots:schedulePath parameters
botIdintegerrequiredThe agent’sid.scheduleIdintegerrequiredThe schedule’sid.
Returns
204The schedule is deleted.
Errors
- 404
SCHEDULE_NOT_FOUNDNo such schedule on this agent.
curl -X DELETE "https://api.cx.na6iq.com/api/v1/bots/42/schedules/42" \
-H "Authorization: Bearer $NATIQ_TOKEN"
const res = await fetch('https://api.cx.na6iq.com/api/v1/bots/42/schedules/42', {
method: 'DELETE',
headers: {
Authorization: `Bearer ${process.env.NATIQ_TOKEN}`,
},
})
if (!res.ok) throw new Error((await res.json().catch(() => ({}))).error?.message ?? `HTTP ${res.status}`)
import os, requests
res = requests.delete(
"https://api.cx.na6iq.com/api/v1/bots/42/schedules/42",
headers={"Authorization": f"Bearer {os.environ['NATIQ_TOKEN']}"},
)
res.raise_for_status()