PUT /{workflowId}/trigger needs workflowBuilder.PUT on an existing webhook trigger keeps the stored key and ignores an apiKey.value you send. Replacing an ingest or schedule trigger keeps that trigger's identity.Authorization: Bearer <API_KEY> with workflowBuilder, on a workflow of the key's community. If-Match is optional: send the revision you read and a stale value is refused with 409 VERSION_CONFLICT, omit it and the change applies as the last write. On a deployed workflow the new trigger takes effect for the next event, immediately, with no extra header.| Failure | Meaning | Recovery |
|---|---|---|
400 VALIDATION_FAILED | Unknown trigger type, or options that do not match the type | Read the trigger schema from GET /catalog and resend. |
{
"data": {
"_id": "65f1a2b3c4d5e6f708192a3b",
"communityId": "65f1a2b3c4d5e6f708192a50",
"name": "VIP bonus",
"status": "draft",
"outgoingWebhookUrl": null,
"createdAt": "2026-10-24T09:15:00.000Z",
"updatedAt": "2026-10-24T12:20:02.000Z",
"trigger": {
"_id": "65f1a2b3c4d5e6f708192a41",
"name": "Trade bonus trigger",
"type": "webhook",
"status": "idle",
"options": { "tokenExchangeEnabled": false, "apiKey": { "enabled": true, "value": "<WEBHOOK_KEY>" } }
},
"nodes": [],
"revision": "68f78253046393414dec404e5442c140bb5db0393a7d1c389ff57bba71585501",
"redactedPaths": []
}
}200 with the whole workflow and its new revision. A readback is the response itself, plus one more GET /{workflowId} to confirm the key is now redacted and the type and options are stored as sent.trigger._id is what step options reference when a step reads the trigger output. It stays the same when the options change and changes only if the trigger is recreated.| Status and code | When | Recovery |
|---|---|---|
400 VALIDATION_FAILED | Unknown trigger type, options that do not match the type, or a batch id path that is not a string | Fix the named field. |
400 REDACTED_VALUE_WITHOUT_STORED_SECRET | A marker arrived where nothing is stored, for example a copied export posted as a new trigger | Send the real value, or set the trigger again without the marker. |
401 AUTHENTICATION_REQUIRED | No key, or an unknown key | Send a valid key. |
403 API_KEY_PERMISSION_DENIED | No workflowBuilder | Grant the permission. |
404 WORKFLOW_NOT_FOUND | Unknown id, or another community's workflow | Use an id from this community. |
409 VERSION_CONFLICT | You sent an If-Match that is not the current revision. meta.currentRevision carries the live value | Re-read and resend. |
415 UNSUPPORTED_MEDIA_TYPE | The body was not declared application/json | Send the header. |429 RATE_LIMITED | The community's API rate limit was reached | Wait for the window to reset, then retry. |500 INTERNAL_ERROR | Unexpected failure | Re-read the trigger before retrying. A retry of the same PUT cannot mint a second key, because the stored key is kept. |503 LOCK_UNAVAILABLE | The write lock store did not answer, so nothing was written | Retry shortly. |409 VERSION_CONFLICT and writes nothing.["__REDACTED__"], and sending a value does not replace it. A lost key is not recoverable by retrying; plan for the replacement path below.apiKey.enabled: true on a trigger that has no stored key asks the platform to mint one. On a trigger that already has a key, the stored value is kept and any value you send is ignored; an empty string is refused.webhook again. No key is stored in between, so the second call mints a new one and shows it once. The trigger id is kept, but this is a replacement of the trigger's authentication, not a rotation, and the sender must be reconfigured.tokenExchangeEnabled and apiKey.enabled are booleans and are never redacted. They tell you the authentication mode without exposing the credential.400 REDACTED_VALUE_WITHOUT_STORED_SECRET.options: null. Send null, not an empty object.GET /{workflowId} to confirm the stored trigger and the redacted key.POST /{workflowId}/validate after the steps reference the new trigger id.POST /{workflowId}/deploy to arm it.curl --location --request PUT 'https://api.returning.ai/v1/workflow-builder/data-workflows/65f1a2b3c4d5e6f708192a3b/trigger' \
--header 'Authorization: Bearer <API_KEY>' \
--header 'If-Match: 162af81f5de4ff2b557a4087de9a9a73a0ed4e621f737787e35a93614cb50d1c' \
--header 'Content-Type: application/json' \
--data '{
"type": "webhook",
"name": "Trade bonus trigger",
"options": {
"tokenExchangeEnabled": false,
"apiKey": {
"enabled": true
}
}
}'{
"data": {
"_id": "65f1a2b3c4d5e6f708192a3b",
"communityId": "65f1a2b3c4d5e6f708192a50",
"name": "VIP bonus",
"status": "draft",
"outgoingWebhookUrl": null,
"createdAt": "2026-10-24T09:15:00.000Z",
"updatedAt": "2026-10-24T09:20:11.000Z",
"trigger": {
"_id": "65f1a2b3c4d5e6f708192a41",
"name": "Trade bonus trigger",
"type": "webhook",
"status": "idle",
"options": {
"tokenExchangeEnabled": false,
"apiKey": {
"enabled": true,
"value": "<WEBHOOK_KEY>"
}
}
},
"nodes": [
{
"_id": "65f1a2b3c4d5e6f708192a3c",
"name": "Read member XP",
"type": "platform-action",
"order": 1,
"continueOnFailure": false,
"retryOnFailure": false,
"options": {
"action": "get-user-fields",
"selectUser": {
"type": "email",
"value": [
"{{65f1a2b3c4d5e6f708192a41.email}}"
]
},
"fields": [
{
"label": "Total XP",
"field": "total_xp"
}
]
}
}
],
"revision": "162af81f5de4ff2b557a4087de9a9a73a0ed4e621f737787e35a93614cb50d1c",
"redactedPaths": []
}
}