Automations

A Space currently has two automation surfaces:

  • Automations (this page): a rule that assigns work to one Agent or Crew and runs a set of instructions.
  • Automation workflows: a canvas for multi-step flows, third-party connectors, or the Anakin Agent node. See Automation workflows.

This page describes Automations only. Do not read the triggers and run history here as the canvas builder.

An Automation is a Space-owned definition for repeatable Agent or Crew work. It combines instructions, an execution assignee, one or more triggers, and run history. Current Automations start Agent or Crew runs directly; they do not create a Task for each run.

Use an Automation when the same well-defined work should be started repeatedly or in response to a predictable event.

What an Automation contains

An Automation stores:

  • Name and instructions describing the goal, steps, constraints, and expected result.
  • Space that owns the Automation.
  • Assignee: an Agent or Crew that performs the work.
  • Priority for the run.
  • Status: Active or Paused.
  • Triggers that start runs.
  • Run history with the source, result, Agent run, and failure reason when available.
  • Owner: required for task-status-change and task-comment automations, defaulting to the trigger author and selectable from Space members; conversations triggered by the Automation are attributed to the Owner.

Run-result notifications

In the Automation settings, choose one or more People to receive run-result notifications. The recipient list applies to the Automation's triggers; leaving it empty turns off run-result notifications for that Automation. Anakin sends one notification when a run reaches a final state: Completed, Failed, Canceled, or Skipped.

When you create an Automation and add a trigger, or add a trigger to an existing Automation, Anakin defaults the Automation creator as a run-result recipient. You can add other People. Editing an existing trigger keeps the current recipients unless you change them; you can replace the list or clear it entirely, including the creator.

Run results also appear in Inbox and are grouped into one row per Automation. The latest successful result normally appears in Other; the latest failed, canceled, or skipped result appears in Primary. Selecting that row opens the Automation's full details in a right-side drawer instead of leaving the Inbox; the run record stays in the history list until you open it. A recipient must still be eligible to access the Automation's Space, so the available People follow the current UI.

An Automation can have multiple triggers. Triggers can also be enabled or disabled independently. A trigger disabled before creation remains disabled after the Automation is saved and refreshed; saving the Automation does not turn it on unexpectedly. Run-result recipients remain configured at the Automation level and apply to all of its triggers, rather than being configured per trigger.

Write useful instructions

Automation instructions should state:

  • The outcome to produce.
  • The inputs or context to inspect.
  • The steps to follow.
  • Where the result should be recorded.
  • Which changes are allowed.
  • When a person must review or decide.

Start with work that is observable and reversible. Review several runs before expanding the Automation's scope.

Trigger types

Schedule

A schedule uses a standard five-field cron expression and a timezone. The UI provides common hourly, daily, weekday, and weekly choices as well as custom cron.

Use schedules for recurring checks, summaries, reminders, or maintenance work.

Task status

A task-status trigger starts when a task moves into one selected Organization status.

Statuses are reusable across Space task type Workflows. Current event matching is Organization-wide: if the same status is used in several Spaces, a matching transition in any of those Spaces can start the Automation. Choose a status that represents the intended scope, and make the instructions verify the triggering task before making changes.

Runs triggered by a Task status change are attributed to that task's conversations. Such a run creates a conversation on the task — visible in the task's chat panel and on its Conversations tab — created by and triggered by the person who changed the status. The run detail also shows a task card that opens the task, and the run's Token usage counts toward that task. Runs from other triggers (schedule, comment keyword, webhook) are unaffected: they still create a run with no linked task, and do not create a Task for each run.

Comment

A comment trigger starts when a task comment contains a configured phrase. Matching is case-insensitive and checks whether the phrase occurs anywhere in the comment.

Current comment matching also listens across the Organization rather than filtering by the Automation's owning Space. Prefer a distinctive slash command or phrase and validate the triggering task in the instructions.

A comment keyword such as /triage starts an Automation Agent. It is not the Space Triage inbox.

Webhook

A webhook trigger creates a secret URL that accepts a POST request from an external service. After you create it, a one-time Copy this URL now dialog appears with Close and Copy URL. The full URL is shown when the trigger is created or regenerated and is not available for later viewing.

Default behavior: the URL is the credential

By default, the URL acts as a bearer secret: anyone with the full URL can start the Automation. A request starts a run only when the trigger is enabled and the Automation is Active (not paused); an accepted request returns 202.

Each webhook trigger also has two optional security settings: Signature verification and IP allowlist. Both are off by default, so existing webhooks keep working with the same URL and the same requests. Triggers that already had a signing secret keep signature verification on, and their existing signatures stay valid.

If the URL is exposed, use Regenerate URL:

  • The old URL stops working immediately and returns 404.
  • The new URL is shown only once. Copy it right away.
  • Security settings — signature verification, the signing secret, and the IP allowlist — are kept as they are. You don't need to set them up again.

Open the security settings

On the Automation detail page, click Edit, find the saved webhook trigger under triggers, and open its Security settings. From there you can Regenerate URL and turn on Signature verification and IP allowlist.

  • Regenerate URL and Reset secret take effect as soon as you confirm them.
  • The Signature verification and IP allowlist switches and the allowlist entries take effect when you save the dialog together with its other fields.
  • A new trigger that hasn't been saved yet has no security settings. Save the trigger first.

Signature verification

With Signature verification on, every request must carry a signature computed with the Signing secret. Requests without a signature, or with a wrong one, are rejected. Even if the URL leaks, nobody can start the Automation without the secret.

Signing secret

  • The signing secret is generated by the system and starts with whsec_. You can't enter your own. It is generated the first time you turn on signature verification.
  • Members who can edit the Automation can view and copy it. It is masked by default.
  • Reset secret generates a new secret, and the old one stops working immediately: requests signed with it return 401 invalid_signature. Be ready to update your callers before you reset.
  • Turning signature verification off keeps the secret. Turning it back on uses the same secret. To change it, use Reset secret.

Request headers

HeaderValue
X-Webhook-TimestampUnix time of the request in seconds, for example 1767225600. A 13-digit millisecond timestamp is also accepted.
X-Webhook-Signaturev1= followed by the signature described below.

Signature algorithm

signed content = value of X-Webhook-Timestamp + "\n" + raw request body
signature      = Base64(HMAC-SHA256(signing secret, signed content))
X-Webhook-Signature: v1=<signature>
  • The HMAC key is the UTF-8 bytes of the full signing secret string, including the whsec_ prefix. Don't decode it.
  • The timestamp in the signed content is the exact string you send in the X-Webhook-Timestamp header. If you send milliseconds, sign the millisecond string.
  • The raw request body is the exact bytes you send. Serialize the body to a string first and use that same content for both signing and sending. Re-formatting the JSON, changing whitespace, or reordering fields after signing breaks the signature.
  • The signature uses standard Base64 (with = padding).

Time window and replay protection

  • Requests whose timestamp is more than 300 seconds before or after the server time are rejected with 401 timestamp_expired. Make sure the sender's clock is accurate.
  • The same signature is accepted only once within 10 minutes. A second request returns 409 replayed_signature. Sign every request again with a fresh timestamp.

GitHub-compatible signatures

With signature verification on, the GitHub-style signature header is also accepted:

X-Hub-Signature-256: sha256=<hex(HMAC-SHA256(signing secret, raw request body))>

This lets you connect a GitHub webhook directly by entering the signing secret as its Secret. It has no timestamp and no replay check, so use X-Webhook-Signature for callers you write yourself. When a request carries both headers, X-Webhook-Signature decides.

Sample code

Every sample reads the webhook URL and signing secret from the WEBHOOK_URL and WEBHOOK_SECRET environment variables and uses only the language's standard library. Set both variables before running a sample:

export WEBHOOK_URL='<your webhook URL>'
export WEBHOOK_SECRET='<your signing secret>'

A successful request prints 202.

curl / Shell (requires openssl and curl)

send-webhook.sh
#!/usr/bin/env bash
set -euo pipefail

BODY='{"event":"deploy.finished","status":"success"}'
TIMESTAMP=$(date +%s)
SIGNATURE=$(printf '%s\n%s' "$TIMESTAMP" "$BODY" \
  | openssl dgst -sha256 -hmac "$WEBHOOK_SECRET" -binary \
  | openssl base64 -A)

curl -sS -X POST "$WEBHOOK_URL" \
  -H "Content-Type: application/json" \
  -H "X-Webhook-Timestamp: $TIMESTAMP" \
  -H "X-Webhook-Signature: v1=$SIGNATURE" \
  --data-binary "$BODY" \
  -w '\nHTTP %{http_code}\n'

Python (Python 3; run python3 send_webhook.py)

send_webhook.py
import base64
import hashlib
import hmac
import json
import os
import time
import urllib.error
import urllib.request

url = os.environ["WEBHOOK_URL"]
secret = os.environ["WEBHOOK_SECRET"]

body = json.dumps({"event": "deploy.finished", "status": "success"}).encode("utf-8")
timestamp = str(int(time.time()))
digest = hmac.new(secret.encode("utf-8"), timestamp.encode("utf-8") + b"\n" + body, hashlib.sha256).digest()
signature = "v1=" + base64.b64encode(digest).decode("ascii")

request = urllib.request.Request(
    url,
    data=body,
    method="POST",
    headers={
        "Content-Type": "application/json",
        "X-Webhook-Timestamp": timestamp,
        "X-Webhook-Signature": signature,
    },
)
try:
    with urllib.request.urlopen(request) as response:
        print(response.status, response.read().decode("utf-8"))
except urllib.error.HTTPError as error:
    print(error.code, error.read().decode("utf-8"))

Node.js (Node.js 18 or later; run node send-webhook.mjs)

send-webhook.mjs
import { createHmac } from "node:crypto";

const url = process.env.WEBHOOK_URL;
const secret = process.env.WEBHOOK_SECRET;

const body = JSON.stringify({ event: "deploy.finished", status: "success" });
const timestamp = Math.floor(Date.now() / 1000).toString();
const signature =
  "v1=" + createHmac("sha256", secret).update(`${timestamp}\n${body}`).digest("base64");

const response = await fetch(url, {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-Webhook-Timestamp": timestamp,
    "X-Webhook-Signature": signature,
  },
  body,
});
console.log(response.status, await response.text());

Go (run go run send_webhook.go)

send_webhook.go
package main

import (
	"bytes"
	"crypto/hmac"
	"crypto/sha256"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
	"strconv"
	"time"
)

func main() {
	url := os.Getenv("WEBHOOK_URL")
	secret := os.Getenv("WEBHOOK_SECRET")

	body, err := json.Marshal(map[string]string{"event": "deploy.finished", "status": "success"})
	if err != nil {
		panic(err)
	}
	timestamp := strconv.FormatInt(time.Now().Unix(), 10)

	mac := hmac.New(sha256.New, []byte(secret))
	mac.Write([]byte(timestamp + "\n"))
	mac.Write(body)
	signature := "v1=" + base64.StdEncoding.EncodeToString(mac.Sum(nil))

	req, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(body))
	if err != nil {
		panic(err)
	}
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("X-Webhook-Timestamp", timestamp)
	req.Header.Set("X-Webhook-Signature", signature)

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()
	respBody, _ := io.ReadAll(resp.Body)
	fmt.Println(resp.StatusCode, string(respBody))
}

Java (JDK 11 or later; run the single file with java SendWebhook.java)

SendWebhook.java
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

public class SendWebhook {
    public static void main(String[] args) throws Exception {
        String url = System.getenv("WEBHOOK_URL");
        String secret = System.getenv("WEBHOOK_SECRET");

        String body = "{\"event\":\"deploy.finished\",\"status\":\"success\"}";
        byte[] bodyBytes = body.getBytes(StandardCharsets.UTF_8);
        String timestamp = String.valueOf(System.currentTimeMillis() / 1000);

        Mac mac = Mac.getInstance("HmacSHA256");
        mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
        mac.update((timestamp + "\n").getBytes(StandardCharsets.UTF_8));
        String signature = "v1=" + Base64.getEncoder().encodeToString(mac.doFinal(bodyBytes));

        HttpRequest request = HttpRequest.newBuilder(URI.create(url))
                .header("Content-Type", "application/json")
                .header("X-Webhook-Timestamp", timestamp)
                .header("X-Webhook-Signature", signature)
                .POST(HttpRequest.BodyPublishers.ofByteArray(bodyBytes))
                .build();
        HttpResponse<String> response =
                HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());
        System.out.println(response.statusCode() + " " + response.body());
    }
}

IP allowlist

With IP allowlist on, only requests whose source IP is on the list are accepted. Other requests return 403 ip_not_allowed. The check runs before the request body is read, and the body of a rejected request is not stored.

  • Enter one entry per line, up to 20 entries.
  • Single IPv4 or IPv6 addresses (such as 203.0.113.10 or 2001:db8::1) and CIDR ranges (such as 203.0.113.0/24 or 2001:db8::/32) are supported.
  • Entries are validated and normalized when you save: a range is rewritten to its network address (for example, 203.0.113.7/24 is saved as 203.0.113.0/24), and duplicates are removed. An invalid entry is reported by name.
  • 0.0.0.0/0 and ::/0 are not allowed, because they would let every address through.
  • IPv4-mapped IPv6 addresses (such as ::ffff:203.0.113.10) are matched as the corresponding IPv4 address.
  • The list can't be empty while the allowlist is on.

If you don't know the caller's outbound IP, send one request first, then use the Source IP in the delivery history or the Last request source IP shown in Security settings.

Error codes

When a security check rejects a request, the response uses the matching HTTP status code, and the reason is in data.reason of the response body:

{
  "success": true,
  "data": {
    "deliveryId": "6f1c2d3e-0000-4000-8000-000000000000",
    "reason": "invalid_signature",
    "status": "rejected"
  }
}
HTTP statusreasonMeaningWhat to do
403ip_not_allowedThe source IP is not on the IP allowlist.Add the caller's outbound IP to the allowlist.
401missing_signatureSignature verification is on but the request has no signature, or it has X-Webhook-Signature without X-Webhook-Timestamp.Send both headers as described above.
401timestamp_expiredThe timestamp is more than 300 seconds away from the server time.Sign again with the current time and fix the sender's clock.
401invalid_signatureThe signature doesn't match: the secret is wrong or was reset, the body changed after signing, or the signed content was built incorrectly.Use the current secret and sign the same body you send.
409replayed_signatureThe same signature was already used within the last 10 minutes.Sign every request again with a fresh timestamp.
429—Too many requests, counted per webhook URL.Retry after the time in the Retry-After response header.

A 429 response has a different body: {"success":false,"error":{"code":"RATE_LIMITED",…}}.

Requests rejected by a security check (403, 401, 409) also appear in the delivery history as Rejected, so you can troubleshoot them. Rate-limited 429 requests are not recorded.

Signature status in delivery history

The webhook delivery history on the Automation detail page shows each request's Source IP and signature status:

Signature statusMeaning
No signatureSignature verification is off, so the request was not checked.
Signature validThe signature check passed.
Signature invalidThe signature doesn't match. A reused (replayed) signature is also recorded as Signature invalid, with the error replayed_signature.
Signature missingSignature verification is on, but the request has no signature or no timestamp.
Timestamp expiredThe timestamp is outside the 300-second window.
IP not allowedThe source IP is not on the IP allowlist. The request was rejected before the signature check.

Manual start

Use Run now to start an Automation directly. This is useful for testing instructions and assignee availability before depending on recurring or event-based triggers.

Runs and health

Run history can report:

  • Running.
  • Completed.
  • Failed.
  • Skipped.

Automation health summarizes the latest useful signal, such as never tested, running, healthy, skipped, or needs attention. Open the run details to inspect the trigger payload, Agent run, output, or recorded failure reason.

Each run record renders as a full conversation reusing the standard chat panel: continue the conversation, ask follow-ups, or add instructions right inside the run, exactly like any other conversation — no need to spin up a separate automation for "just a bit more".

A completed status describes the Automation run, not a promise that every downstream business outcome was correct. Review the Agent run output when the work matters.

Unattended sessions

Agent sessions started by Automations — as well as IM bots and workflows — are unattended sessions: nobody sits at the other end of the conversation, so the Agent runs unattended:

  • no permission-approval cards: the Agent's approval-level settings do not apply to these sessions;
  • no questions to humans: structured questions are unavailable in these sessions;
  • if the model still tries to ask or wait for authorization, the turn fails fast with a reason instead of hanging on a human wait (which can run up to 2 hours).

For flows that need the Agent to interact with a person — questions, confirmations, approvals — use an attended session: a normal conversation or a task conversation. Avoid instructions like "ask me before continuing".

Pause, resume, or delete

Pause an Automation to stop new triggered runs while you revise instructions, triggers, or the assigned Agent or Crew. Resume it when the configuration is ready.

Delete an Automation only when its definition and run history are no longer needed. The current UI can display older Archived records, but it does not provide an action to archive an Automation.

Practical safety checks

Before relying on an Automation:

  1. Run it manually.
  2. Confirm the Agent or Crew has the required access.
  3. Inspect the Agent run and output.
  4. Test each trigger separately.
  5. Use a narrow status or distinctive comment phrase.
  6. Keep secret webhook URLs outside source control and public messages.