Skip to main content
Webhooks let you receive an HTTP callback when a run reaches a terminal state, so you don’t need to poll for results.

How It Works

  1. Pass a webhook_url when you create a run
  2. When the run finishes (COMPLETED, FAILED, or CANCELLED), TinyFish sends a POST request to your URL
  3. The payload contains the full run data — the same shape as GET /v1/runs/{id}
Webhook delivery is non-blocking. If your endpoint is down, the run still succeeds — you can always fetch the result via the API.

Configuration

Add webhook_url to any run creation endpoint. The URL must use HTTPS.
curl -X POST "https://agent.tinyfish.ai/v1/automation/run-async" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com",
    "goal": "Extract the page title",
    "webhook_url": "https://your-server.com/webhooks/tinyfish"
  }'
Webhooks are supported on all run endpoints: /run, /run-async, /run-sse, and /run-batch.

Payload

Your endpoint receives a POST request with Content-Type: application/json:
{
  "event": "run.completed",
  "run_id": "4562765d-156e-4c47-b217-55b3ec4a9720",
  "status": "COMPLETED",
  "data": {
    "run_id": "4562765d-156e-4c47-b217-55b3ec4a9720",
    "status": "COMPLETED",
    "goal": "Extract the page title",
    "created_at": "2026-03-25T10:30:00Z",
    "started_at": "2026-03-25T10:30:05Z",
    "finished_at": "2026-03-25T10:30:45Z",
    "num_of_steps": 3,
    "result": { "title": "Example Domain" },
    "error": null,
    "streaming_url": "https://tf-abc123.fra0-tinyfish.unikraft.app/stream/0",
    "browser_config": {
      "proxy_enabled": false,
      "proxy_country_code": null
    },
    "steps": [
      {
        "id": "09de224d-fb2b-45b7-b067-585726194a2e",
        "timestamp": "2026-03-25T10:30:05Z",
        "status": "COMPLETED",
        "action": "Navigate to https://example.com",
        "screenshot": null,
        "duration": "1.2s"
      }
    ]
  }
}

Event Types

EventWhen
run.completedRun finished (check result for data)
run.failedInfrastructure error occurred
run.cancelledRun was manually cancelled

Payload Fields

FieldTypeDescription
eventstringEvent type (e.g. run.completed)
run_idstringUnique run identifier
statusstringCOMPLETED, FAILED, or CANCELLED
dataobjectFull run data — same shape as GET /v1/runs/{id}
data.resultobject | nullExtracted data (when completed)
data.errorobject | nullError details (when failed)
data.stepsarrayList of actions the agent took
Screenshots are not included in webhook payloads to keep the payload size small. To get screenshots, call GET /v1/runs/{id}?screenshots=base64 after receiving the webhook.

Error Payload

When a run fails, data.error contains details about what went wrong:
{
  "event": "run.failed",
  "run_id": "run-2",
  "status": "FAILED",
  "data": {
    "error": {
      "message": "Site blocked access",
      "category": "AGENT_FAILURE"
    },
    "result": null
  }
}

Retry Behavior

TinyFish retries failed webhook deliveries automatically:
BehaviorDetail
Total attempts4 (1 initial + 3 retries)
BackoffExponential: 1s, 2s, 4s
Timeout10 seconds per attempt
4xx responsesNo retry (fails immediately)
5xx responsesRetried with backoff
Network errorsRetried with backoff

Receiving Webhooks

Here’s how to handle webhook events on your server:
import express from "express";

const app = express();
app.use(express.json());

app.post("/webhooks/tinyfish", (req, res) => {
  const { event, run_id, data } = req.body;

  switch (event) {
    case "run.completed":
      console.log(`Run ${run_id} completed:`, data.result);
      // Process the result
      break;
    case "run.failed":
      console.error(`Run ${run_id} failed:`, data.error?.message);
      // Handle the error
      break;
    case "run.cancelled":
      console.log(`Run ${run_id} was cancelled`);
      break;
  }

  // Respond quickly — TinyFish has a 10s timeout
  res.status(200).send("OK");
});

Best Practices

TinyFish waits up to 10 seconds for your response. Do heavy processing asynchronously — acknowledge the webhook immediately and handle the data in the background.
In rare cases (network retries, server restarts), you may receive the same webhook more than once. Use run_id to deduplicate.
For sensitive workflows, confirm the webhook data by calling GET /v1/runs/{run_id} before acting on the payload.
Any 2xx response acknowledges the webhook. Non-2xx responses trigger retries (except 4xx, which fail immediately).

Runs

Understand run lifecycle and statuses

Endpoints

Choose sync, async, or streaming