Skip to content
Tireless Crew Docs

Destinations

A destination sends an agent’s output somewhere else each time a run completes: to an email address, a webhook or a Slack channel.

To add one, open the agent from Agents, go to Deliver output to in the Your setup card, and press Add destination. An agent can have several. You can pause and resume each one or change its address later. To change its type, remove it and add a new one.

Output is delivered only for runs that complete. A failed delivery is retried twice: once after 30 seconds and again two minutes after that.

Email

Sends the output to one email address. The subject is [Tireless Crew] agent name finished, and the email contains the output, the agent name, the run ID and when it finished.

Webhook

Sends the output as JSON to an address you control, so you can pass results into your own systems.

Webhook destination settings
SettingDescription
Webhook URL The address that receives the request. It must be a public HTTPS address; private and local addresses are refused.
Secret Optional. When set, each request is signed with it (see below). Leave it blank for no signature.

Request format

The platform sends a POST request with Content-Type: application/json and this body:

{
  "agent":       "Abandoned Cart Recovery",
  "agent_id":    "uuid-of-the-agent",
  "run_id":      "uuid-of-the-run",
  "status":      "completed",
  "output":      "Subject: Your cart is still waiting...\n\nHi Sarah...",
  "finished_at": "2026-06-09T14:32:00+00:00"
}

Respond with a 2xx status. A 4xx or 5xx status, or no response within 10 seconds, counts as a failed delivery.

Verifying the signature

When you set a secret, each request carries an X-Signature header: an HMAC-SHA256 digest of the raw request body, prefixed with sha256=.

X-Signature: sha256=3b4c2d1e9f...

Check it in your receiver before you trust the payload.

PHP

<?php

function verifyWebhookSignature(string $body, string $header, string $secret): bool
{
    $expected = 'sha256=' . hash_hmac('sha256', $body, $secret);
    return hash_equals($expected, $header);
}

$body      = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_SIGNATURE'] ?? '';

if (! verifyWebhookSignature($body, $signature, 'YOUR_SECRET')) {
    http_response_code(401);
    exit('Invalid signature');
}

$payload = json_decode($body, true);
// handle $payload...

Node.js

const crypto = require('crypto');

function verifyWebhookSignature(body, header, secret) {
    const expected = 'sha256=' + crypto
        .createHmac('sha256', secret)
        .update(body)
        .digest('hex');
    return expected.length === header.length && crypto.timingSafeEqual(
        Buffer.from(expected),
        Buffer.from(header)
    );
}

// Express example
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
    const signature = req.headers['x-signature'] || '';
    if (!verifyWebhookSignature(req.body, signature, 'YOUR_SECRET')) {
        return res.status(401).send('Invalid signature');
    }
    const payload = JSON.parse(req.body.toString());
    // handle payload...
    res.sendStatus(200);
});
Compare signatures with hash_equals (PHP) or timingSafeEqual (Node.js). A plain === comparison is open to timing attacks.

Slack

Posts the output to a Slack channel through an incoming webhook. Create the webhook in your Slack app’s settings under Incoming Webhooks, then paste its URL into Slack incoming webhook URL. It must be an HTTPS address.

The message starts with the agent’s name in bold followed by “finished:”, then the output. Output longer than 3,000 characters is cut short and ends with “…”; the full output stays on the run’s page.