Verify Webhook requests and reliably process events that may be delivered more than once.
Webhook is currently in Beta. APIs, fields, and behavior may change in future versions.
HTTP request
When an event occurs, Qoder Cloud Agents sends an HTTP POST request to the Endpoint URL:
| Header | Description |
|---|---|
Webhook-ID | Event ID. It remains the same across retries and can be used as the consumer idempotency key. |
Webhook-Event-Type | Event type. |
Webhook-Timestamp | Unix timestamp in seconds used to generate the signature. |
Webhook-Signature | Request signature, currently formatted as v1,<base64>. |
2xx response indicates successful processing. Verify the signature, write the event to your reliable queue, and return the response promptly.
Event structure
Forward business events use a common envelope:
| Field | Type | Description |
|---|---|---|
type | string | Always event. |
id | string | Webhook event ID. Matches Webhook-ID. |
created_at | string | Event time, in RFC 3339 format. |
data.type | string | Event type. Matches Webhook-Event-Type. |
data.id | string | ID of the resource that emitted the event. |
data.schema_version | integer | Event data version. Currently 1. |
Schedule events
The data of all four Schedule events below includes source and source_session_id. See Schedule creation source for their meaning. Run events inherit the parent Schedule's creation source.
Events newly enqueued after service deployment always include both fields. Historical events and their retries may omit them, so clients must tolerate missing fields. If needed, query the source through Get a schedule or Get a schedule run.
forward.schedule.created
Sent when a Schedule is created successfully:
trigger_policy_type and next_trigger_at are returned only when values are available.
forward.schedule.archived
Sent when a Schedule first changes from unarchived to archived. data.status is always archived.
This covers the following archive paths:
- Calling the archive API or the
delete_forward_scheduletool in a Session. - Batch archiving, with one event for each Schedule actually archived by that operation.
- Automatic archiving after a one-time task finishes, or when a trigger plan has no next run because of
stop_at. - Cascaded archiving when clearing an Identity, and archiving triggered by unavailable dependencies. Pausing a Schedule alone does not send this event.
| Field | Description |
|---|---|
schedule_id | ID of the archived Schedule, the same as data.id. |
identity_id, template_id | Identity and Template that the Schedule belongs to. |
source, source_session_id | The Schedule's creation source, not the source of this archive operation. For API creation, these are api and null; for tool creation, they are tool and the ID of the Session that created it. |
status | Always archived. When querying a Schedule object, continue to determine its archive state from a non-null archived_at. |
archived_at | Actual archive time in RFC 3339 format. The outer created_at corresponds to this time. |
Webhook-ID. Previously archived historical data does not receive backfilled notifications.
This event means the Schedule is archived. It does not mean existing Runs have finished or that all other cleanup steps for clearing an Identity are complete. Delivery order is not guaranteed between archive, creation, and Run result notifications. When synchronizing local state, do not let a late creation notification reactivate an archived task.
This is a separate Webhook notification and does not add an archive event to the Session. Existing Endpoints must add forward.schedule.archived to events; Endpoints subscribed to * receive it automatically.
forward.schedule_run.succeeded
Sent when a Schedule Run completes successfully. data.status is completed.
forward.schedule_run.failed
Sent when a Schedule Run fails. The structure is the same as the success event, data.status is failed, and error_type may be included:
Managed Agents resource events
Forward also supports subscribing to Session, Session Thread, Agent, Environment, Memory Store, Vault, and Vault Credential events. See Supported public events for full event names and triggers.
These events use the same outer envelope, but data carries only a resource reference. It does not include the schema_version, business status, or result body of Schedule events. For example, when a Session becomes idle:
| Event family | data.id | Additional fields |
|---|---|---|
session.status_*, session.updated, session.deleted | Session ID | None. |
session.thread_* | ID of the parent Session | session_thread_id: the specific thread ID. |
agent.* | Managed Agent ID | None. |
environment.* | Environment ID | None. |
memory_store.* | Memory Store ID | None. |
vault.* | Vault ID | None. |
vault_credential.* | Vault Credential ID | vault_id: ID of the parent Vault. |
* in this table only groups event families. It is not a prefix wildcard you can put in events. After a notification arrives, query the current details by resource ID. Resources referenced by deletion events may no longer be queryable. session.status_idled does not mean the business run succeeded; judge the outcome from the Session output and status.
webhook.test does not use the resource event structure above. See Send a test event for its payload.
Verify signatures
The signing_secret returned when an Endpoint is created verifies request signatures. The signed content combines three parts:
Webhook-Timestampdiffers from the current time by no more than five minutes.Webhook-Signaturecontains at least one validv1signature.- A previously processed
Webhook-IDdoes not repeat side-effecting business operations.
Retries and idempotency
Webhook delivery is at least once. The system may retry the same event after a network failure, timeout, or non-2xx response.
- Deduplicate using
Webhook-ID. Do not generate an idempotency key from the arrival time. - Return success for duplicate requests with the same
Webhook-ID, and do not repeat side effects. - Return non-
2xxon processing failure. Return2xxafter successful processing or when the event was already processed.

