Skip to content
Console
Browse documentation
Guide

Event delivery to your agent

Receive signed operational events at an endpoint you own.

On this page

An idle MCP connection cannot receive a push. Give Anectico a public HTTPS endpoint you own. Anectico sends a signed JSON event there when a subscribed change happens. Your receiver can wake your own agent. Anectico does not investigate, decide, fix anything, or run your agent.

A destination belongs to one project and the API key that creates it. Use a management key created by a person in Agents and keys, scoped to that project. An intake key, application key, key created by another key, or OAuth connection cannot create a destination. A key sees only its own destinations. A workspace owner or admin can also inspect, pause, or delete them in the Console. The receiver URL cannot be changed. Create a new destination to move to another endpoint.

Events and schemas

Every document has version: 1, id, type, occurred_at, delivered_at, attempt, workspace_id, project_id, subject, and facts. The JSON Schema contains a separate definition for every type. Unknown fields are forbidden. IDs are UUIDs. Times are UTC. occurred_at is the original change time. delivered_at and attempt change on a resend. attempt is the reserved attempt number, from 1 to 8.

Type and schema definition The same occurrence means Subject and facts Read scope
issue.first_seen ($defs/issue.first_seen) One issue and its original first-seen time issue; state new errors:read
issue.regressed ($defs/issue.regressed) One issue and its regression time issue; state regressed errors:read
alert.fired ($defs/alert.fired) One alert and its original trigger time alert; state firing; optional severity info, warning, or critical alerts:read
alert.recovered ($defs/alert.recovered) One alert and its measured recovery time alert; state recovered alerts:read
incident.opened ($defs/incident.opened) One incident and its creation time incident; state open; optional severity sev1 to sev4 incidents:read
incident.resolved ($defs/incident.resolved) One incident and its resolution time incident; state resolved incidents:read
test ($defs/test) One explicit test request; a new ID for each request destination; state test event_destinations:write

Subscribe to issue.first_seen, issue.regressed, alert.fired, or incident.opened. Set include_recovery: true on an alert or incident subscription to also receive its recovery or resolution. Recovery is off by default. Manually closing an alert is not a measured recovery. Waking a snoozed issue is not a regression. There are no separate release or metric watch event types yet. Workspace-wide notifications have no project destination and are not sent.

For operational events, the ID is deterministic. Join these lines with \n, with no final newline: anectico.agent-event.v1, type, workspace ID, project ID, subject ID, original UTC transition time in RFC3339Nano format. Hash the UUID URL namespace bytes (6ba7b811-9dad-11d1-80b4-00c04fd430c8) followed by those UTF-8 bytes using SHA-256. Take the first 16 bytes, set the UUID version nibble to 5 and RFC variant bits, and format as a UUID. This is Anectico's specified SHA-256 derivation; it does not use the usual UUIDv5 SHA-1 algorithm. Re-evaluating or re-delivering the same change keeps this ID.

Documents contain closed state and severity values, not error messages, log lines, user-written titles, labels, person IDs, emails, or event properties. They do not contain measured counts. Missing severity is unknown. A proof URL is present only for an issue or incident with a proof page. Your agent reads details with its own key and permissions through MCP or the CLI. Destination documents never go to your ordinary notification channels. Your ordinary workspace notifications never go to an event destination.

Set up a destination

The key needs event_destinations:write, event_destinations:read, and the read scope for each subscribed subject. MCP writes also need mcp:write; MCP reads need mcp:read. The recorded scope ceiling is the key’s concrete scopes at creation. Granting a new read scope later does not widen an old destination. Create a new destination if the subscription needs a scope outside that ceiling. Removing a scope still stops existing subscriptions.

Event delivery and the current subject reads are available on every plan. Creating or responding to an incident still follows its own plan rules.

Create a file containing the receiver and subscriptions:

{
  "url": "https://receiver.example.test/anectico",
  "subscriptions": [{"type": "alert.fired", "include_recovery": true}]
}

Use anectico event-destinations create --file destination.json. The active project supplies the project ID. The response contains the new destination and a signing secret shown once. Save it securely in your receiver. Get, list, receipts, and later reads never return that secret. If the create response is lost, inspect the list before retrying: creating again makes another destination. Rotate the secret if you could not save it.

Through MCP, discover create_agent_destination with list_write_actions, then use execute_external_action with action: "create_agent_destination". The first call previews the owner key, project, exact URL, subscriptions, and active state without creating anything. Approve those fields using the returned confirmation token. Only the confirmed response shows the new secret once. Do not include it in notes, audit text, or a conversation transcript.

Verify the signature

Headers are Anectico-Event-ID, Anectico-Timestamp, and Anectico-Signature. The signature header has the form t=1791536400,v1=<lowercase hexadecimal digest>. The signed bytes are the decimal Unix timestamp, one ASCII period, then the exact raw request body bytes. Compute HMAC-SHA-256 using the signing secret's literal UTF-8 bytes. Do not decode the secret as base64 and do not parse or reserialize the JSON before checking it.

Reject timestamps more than 300 seconds from your receiver's clock. Compare signatures in constant time. Then parse the validated document, require version 1, check the schema and that the body ID matches the header ID, and deduplicate by event ID. Save the ID before starting your agent. A timestamp tolerance alone does not remove duplicates inside that window.

This Python example checks a request using the current secret and, during rotation, the previous one. Both are obtained from your receiver's secure configuration:

import hashlib
import hmac
import json
import time

def verify(raw_body, signature_header, timestamp_header, event_id_header, secrets):
    if len(raw_body) > 8192:
        raise ValueError("body too large")
    fields = [part.split("=", 1) for part in signature_header.split(",")]
    timestamps = [value for name, value in fields if name == "t"]
    signatures = [value for name, value in fields if name == "v1"]
    if len(timestamps) != 1 or timestamps[0] != timestamp_header:
        raise ValueError("timestamp mismatch")
    timestamp = timestamps[0]
    if not timestamp.isascii() or not timestamp.isdecimal():
        raise ValueError("invalid timestamp")
    if abs(time.time() - int(timestamp)) > 300:
        raise ValueError("expired signature")
    signed = timestamp.encode("ascii") + b"." + raw_body
    valid = False
    for secret in secrets:
        expected = hmac.new(secret.encode("utf-8"), signed, hashlib.sha256).hexdigest()
        for signature in signatures:
            valid |= hmac.compare_digest(expected, signature)
    if not valid:
        raise ValueError("invalid signature")
    event = json.loads(raw_body)
    if event["id"] != event_id_header or event["version"] != 1:
        raise ValueError("event mismatch")
    return event  # Validate against the published schema before accepting it.

Public test vector: secret example-only; timestamp 1791536400; body bytes {"id":"test"} with no newline. The digest is 03ba7b3f1fdfcd42bf500da12b56741339d5c0cf355a394cff885e2517b8a255. The small body demonstrates signing only; it is not a complete event document.

Use anectico event-destinations rotate-secret <id> --yes or the confirmed MCP action rotate_agent_destination_secret. The new secret is shown once. For one hour Anectico sends two v1 values: one with the new secret and one with the previous secret. A receiver accepts either. Install the new secret before removing the old one. Another rotation is refused while the overlap is active.

Retries, stops, and limits

Delivery is at least once. A lost response can cause the same event to arrive again. Ordering is not guaranteed. Reply with any 2xx status to accept a request. Redirects are failures and are never followed. Response bodies are not kept.

A send times out after 10 seconds. Each attempt has a 20-second work budget. DNS validation also happens on each attempt and the connection pins the validated address. Private, loopback, link-local, and other forbidden addresses are refused. A destination reserves at most one send per second. Bodies are at most 8 KiB. A key can own 5 destinations and a project can have 20. There are at most 4 base subscriptions per destination. Attempt reads default to 50 records, allow at most 100 per page, and refuse cursor offsets above 10,000. The final allowed page can contain records 10,001 to 10,100. This is a navigation bound, not a total. The worker claims at most six deliveries per pass, with at most three due rows per workspace. Both event and ordinary-notification retries share eight concurrent sends per process, with at most one in flight per workspace. A slow receiver cannot fill all eight places. These limits are bounds, not measured activity totals.

Events have no delivery-order guarantee. Independent workspaces run in parallel; sends to a workspace are sequential within each process. Different replicas still serialize sends to a destination. Receivers must deduplicate event IDs and use the event timestamps when order matters.

Transport and HTTP failures retry with exponential backoff: a 30-second starting delay, doubled per failed attempt up to one hour, then jittered to 50–100% of that delay. There are at most 8 reserved attempts. A document older than 24 hours from occurred_at is dead-lettered. Five consecutive failed sends disable the destination. A successful send resets that count. An address refusal is terminal. The owner can read the reason and explicitly resume a disabled destination, which resets the failure count.

Before every attempt Anectico checks the project, the current owner key, its recorded scope ceiling, and the required subject read scope. Revoked, frozen, expired, suspended, or contained keys stop deliveries. A deleted project, removed scope or subscription, paused destination, or unavailable signing authority stops that attempt with a recorded reason. Such stops are terminal, not transport retries. Making the key valid or resuming the destination does not replay stopped events. A queued event that has not reached a stop may still send under the normal retry rules while it is less than 24 hours old. An already in-flight request cannot be recalled; freezes prevent further attempts after the next authority check.

Use anectico event-destinations attempts <id> or list_agent_delivery_attempts through execute_read_action to read recorded times, outcome classes, HTTP status when a response was received, duration, and reasons. unknown means an attempt was reserved but its outcome was not recorded, for example after a process interruption. It is neither success nor failure. The result describes the page's population and time window. It does not claim a total.

Test and manage

Use anectico event-destinations test <id> --yes or the confirmed test_agent_destination action to queue a signed type: "test" document. It has its own event ID, goes through the same checks and retry path, and does not indicate an operational problem. Read its attempts to confirm acceptance; a queue receipt alone does not prove delivery.

anectico event-destinations list, get <id>, and update <id> --file subscriptions.json inspect or replace subscriptions. pause <id> --yes, resume <id> --yes, and delete <id> --yes control delivery. Deleting a destination removes its queued deliveries and attempt history. Project or workspace deletion removes these records too. Event documents carry no tracked-person data, so there is no person archive or erasure entry for them.

The read actions are list_agent_destinations, get_agent_destination, and list_agent_delivery_attempts through execute_read_action. The other confirmed write actions are update_agent_destination, pause_agent_destination, resume_agent_destination, and delete_agent_destination; discover their correct write gateway with list_write_actions.

Ask your agent

Use create_agent_destination through execute_external_action, then list_agent_delivery_attempts through execute_read_action. The CLI path is anectico event-destinations create --file destination.json and anectico event-destinations attempts <id>.

“When an alert fires, deliver it to my endpoint at this URL so my system can wake you. Include recoveries. Show me the project, owner key and subscriptions before creating it. After I approve, help me store the signing secret securely and send a test event. Check the recorded outcome.”

“Pause this key's event destination, and show me why its last attempt stopped.”

Use the anectico-alerts-setup skill for this optional setup step. Deliver to an endpoint only when the person asks for it. The foundation anectico-cli skill helps find this capability.