โ† Back to blog

Webhooks for AI Agent Pipelines: Trigger URLs When Jobs Complete

July 12, 2026 ยท Eric ยท 6 min read

Your pipeline just finished processing a batch of images โ€” now what? Polling for completion wastes cycles. Manually checking the dashboard doesn't scale. What you really want is a push notification โ€” the system tells you when it's done, with the results attached.

That's exactly what Agent Media Tools webhooks provide. Register a URL, subscribe to pipeline events, and receive a POST request with a JSON payload every time a job completes or fails. No polling, no custom cron jobs, no wasted compute.

Here's everything you need to know about setting up webhooks for your agent pipelines โ€” including payload format, HMAC verification, retry behavior, and receiving the webhooks in Python, Node.js, and n8n.

Why Use Webhooks for Your Pipeline?

Agent Media Tools pipelines run asynchronously. When you kick off a job โ€” whether it's resizing a batch of images, compressing files, or running a multi-step pipeline โ€” the system processes it in the background and sends you the result when it's ready.

Without webhooks, you'd have to poll an endpoint repeatedly to check job status. That's wasteful, slow, and adds complexity. Webhooks flip the model: the system calls you.

Architecture Overview

PropertyValue
TransportHTTP POST with JSON body
Eventspipeline.completed, pipeline.failed
Signature headerX-Webhook-Signature (HMAC-SHA256, optional)
Max retries0โ€“5 (default 3)
Retry schedule30s โ†’ 2 min โ†’ 5 min (exponential backoff)
Delivery logView all attempts with response body and status code
Auth requiredYes โ€” login to register/manage webhooks

Supported Events

Each webhook subscribes to one or more event types. When a pipeline run produces an event matching your subscription, the system POSTs a payload to your URL.

pipeline.completed

Fires when a pipeline run finishes successfully. The payload includes the run ID, pipeline configuration, output URLs (compressed images, generated PDFs, etc.), and timing details.

pipeline.failed

Fires when a pipeline run encounters an error. The payload includes the error message, the step that failed, and any partial outputs that were generated before the failure.

Example Payload

Here's what a pipeline.completed webhook POST looks like:

{
  "event": "pipeline.completed",
  "run_id": "run_abc123def456",
  "pipeline": "resize-compress-batch",
  "status": "success",
  "started_at": "2026-07-12T14:30:00Z",
  "completed_at": "2026-07-12T14:30:08Z",
  "duration_ms": 8234,
  "outputs": [
    {
      "type": "image",
      "url": "https://agentmediatools.com/download/output_001.webp",
      "size_bytes": 142380
    },
    {
      "type": "image",
      "url": "https://agentmediatools.com/download/output_002.webp",
      "size_bytes": 201455
    }
  ],
  "metadata": {
    "input_count": 10,
    "total_input_size": 4200000,
    "total_output_size": 1230000,
    "savings_percent": 70.7
  }
}

And a pipeline.failed payload:

{
  "event": "pipeline.failed",
  "run_id": "run_def789ghi012",
  "pipeline": "compress-batch",
  "status": "failed",
  "started_at": "2026-07-12T14:35:00Z",
  "completed_at": "2026-07-12T14:35:12Z",
  "duration_ms": 12340,
  "error": {
    "step": "compress_image",
    "message": "File size exceeds 20 MB limit",
    "input_file": "large_video_poster.png"
  },
  "partial_outputs": []
}

Receiving Webhooks (Python)

Here's a minimal Flask webhook receiver that accepts POST requests and verifies the HMAC signature:

import os
import json
import hmac
import hashlib
from flask import Flask, request, jsonify

app = Flask(__name__)

# Set this to the secret you configured when creating the webhook
WEBHOOK_SECRET = os.environ.get("WEBHOOK_SECRET", "your-secret-here")

def verify_signature(payload_body, signature_header):
    """Verify HMAC-SHA256 signature of the raw request body."""
    if not signature_header:
        return False
    expected = hmac.new(
        WEBHOOK_SECRET.encode(),
        payload_body,
        hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(f"sha256={expected}", signature_header)

@app.route("/webhook", methods=["POST"])
def handle_webhook():
    raw_body = request.get_data()
    sig = request.headers.get("X-Webhook-Signature", "")

    if not verify_signature(raw_body, sig):
        return jsonify({"error": "Invalid signature"}), 401

    payload = json.loads(raw_body)
    event = payload.get("event")

    if event == "pipeline.completed":
        print(f"โœ… Pipeline {payload['run_id']} completed in {payload['duration_ms']}ms")
        print(f"   Outputs: {len(payload['outputs'])} files")
        print(f"   Savings: {payload['metadata']['savings_percent']}%")

        # Do something with the outputs โ€” upload to S3, post to Discord, etc.

    elif event == "pipeline.failed":
        print(f"โŒ Pipeline {payload['run_id']} failed at step '{payload['error']['step']}'")
        print(f"   Error: {payload['error']['message']}")

        # Send an alert

    return jsonify({"ok": True}), 200

if __name__ == "__main__":
    app.run(port=8080)

Run it with python webhook_receiver.py, then register http://your-server:8080/webhook in the webhook UI.

Receiving Webhooks (Node.js / Express)

Same idea in Node.js without the signature verification boilerplate:

import express from "express";
import crypto from "crypto";

const app = express();
const SECRET = process.env.WEBHOOK_SECRET || "your-secret-here";

app.use(express.raw({ type: "application/json" }));

app.post("/webhook", (req, res) => {
  const sig = req.headers["x-webhook-signature"] || "";
  const expected = "sha256=" +
    crypto.createHmac("sha256", SECRET).update(req.body).digest("hex");

  if (!crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) {
    return res.status(401).json({ error: "Invalid signature" });
  }

  const payload = JSON.parse(req.body.toString());
  console.log("Received event:", payload.event, "for run:", payload.run_id);
  res.json({ ok: true });
});

app.listen(8080);

curl Test โ€” Simulate a Webhook

๐Ÿ’ก Windows users: Run these from Git Bash or WSL โ€” PowerShell's curl is an alias for Invoke-WebRequest. Or use Invoke-RestMethod with the same URL/body.

You can test your webhook receiver by POSTing a sample payload directly:

curl -X POST http://localhost:8080/webhook \
  -H "Content-Type: application/json" \
  -H "X-Webhook-Signature: sha256=YOUR_COMPUTED_SIG" \
  -d '{
    "event": "pipeline.completed",
    "run_id": "test_run_001",
    "pipeline": "test-pipeline",
    "status": "success",
    "started_at": "2026-07-12T12:00:00Z",
    "completed_at": "2026-07-12T12:00:05Z",
    "duration_ms": 5120,
    "outputs": [
      {"type": "image", "url": "https://example.com/result.webp", "size_bytes": 88000}
    ],
    "metadata": {
      "input_count": 3,
      "total_input_size": 1200000,
      "total_output_size": 264000,
      "savings_percent": 78
    }
  }'

Tip: When testing locally, you can skip signature verification by not setting a secret on the webhook. The X-Webhook-Signature header is only sent when you configure a secret key.

HMAC Signature Verification

Every webhook can optionally sign its payloads with HMAC-SHA256. When you create a webhook in the UI, you can assign a secret key. All subsequent deliveries include an X-Webhook-Signature header:

X-Webhook-Signature: sha256=6a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f

To verify:

  1. Take the raw request body (as bytes โ€” not parsed JSON)
  2. Compute HMAC-SHA256(secret, body)
  3. Encode as hexadecimal
  4. Prefix with sha256=
  5. Compare with the header value using a constant-time comparison

The Python and Node.js examples above both implement this correctly, using hmac.compare_digest / crypto.timingSafeEqual for timing-safe comparison.

Retry Behavior

Webhook delivery failures are automatic and transparent. When a delivery fails (non-2xx response or network timeout), the system retries on this schedule:

AttemptDelay
1Immediate
230 seconds
32 minutes
45 minutes
55 minutes (if retries > 3)

You configure the maximum number of retries per webhook (0โ€“5). After exhausting all retries, the delivery is marked as failed. You can always view the full delivery log in the webhook UI to see each attempt, including the HTTP status code and response body.

Integrating with n8n

n8n is one of the best platforms for consuming webhooks. Here's a 3-node workflow that captures pipeline completions and sends a Discord notification:

  1. Webhook trigger โ€” create a Webhook node listening on POST. Copy the production URL and register it in the Agent Media Tools webhook UI.
  2. IF node โ€” check if event is pipeline.completed or pipeline.failed
  3. Discord node โ€” POST a formatted message to your Discord webhook URL with run ID, duration, and outputs

Because the payload includes all outputs and metadata, you can route successful results to S3, Google Sheets, or an email notification โ€” all without writing a single line of code.

Using the MCP Server for Dynamic Webhooks

If you're using Claude Desktop, Cursor, or any MCP-compatible IDE with the Agent Media Tools MCP server, you can register webhooks dynamically from your chat:

"Create a webhook that calls https://my-server.com/webhook when my pipeline completes"

The MCP server exposes the full webhook API โ€” create, list, update, test, and delete. This makes it trivial for AI agents to set up their own callback URLs on the fly.

Testing Webhooks

Every webhook has a Test button in the UI that sends a sample pipeline.completed payload to your URL. This is useful for:

To test, navigate to the Webhooks page, click Log on any webhook, and you'll see every delivery attempt with timestamps, status codes, and response bodies.

Pricing and Limits

Webhook functionality is available on all plans:

Rate limits reset at midnight UTC. The X-Webhook-Delivery-Id header on each request uniquely identifies every delivery attempt.

Putting It All Together: Real-World Flow

Here's a complete end-to-end example using the tools mentioned in this post:

  1. Create a pipeline in the Agent Media Tools UI that resizes images and converts them to WebP
  2. Register a webhook pointing to your n8n endpoint (or your Flask/Express receiver)
  3. Subscribe to both pipeline.completed and pipeline.failed
  4. Trigger the pipeline from curl, the CLI, or via the MCP server
  5. Your receiver gets a POST with the results ~5โ€“10 seconds later
  6. Forward the results to Discord, Slack, S3, or your own database

No polling. No manual checks. Just push-based delivery from start to finish.

Related Guides

Try Webhooks Now

Log in at agentmediatools.com, navigate to the Webhooks page, and create your first webhook. Set up the Python receiver above, trigger a pipeline, and watch the POST arrive in real time.

โ†’ Visit Agent Media Tools  ยท  ๐Ÿ”” Webhooks UI  ยท  ๐Ÿ’ฌ Discord

Tags: webhooks, agent pipelines, automation, async jobs, HMAC signing, n8n, Discord integration