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.
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.
| Property | Value |
|---|---|
| Transport | HTTP POST with JSON body |
| Events | pipeline.completed, pipeline.failed |
| Signature header | X-Webhook-Signature (HMAC-SHA256, optional) |
| Max retries | 0โ5 (default 3) |
| Retry schedule | 30s โ 2 min โ 5 min (exponential backoff) |
| Delivery log | View all attempts with response body and status code |
| Auth required | Yes โ login to register/manage webhooks |
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.
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.
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.
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": []
}
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.
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);
๐ก 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.
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:
HMAC-SHA256(secret, body)sha256=The Python and Node.js examples above both implement this correctly, using hmac.compare_digest / crypto.timingSafeEqual for timing-safe comparison.
Webhook delivery failures are automatic and transparent. When a delivery fails (non-2xx response or network timeout), the system retries on this schedule:
| Attempt | Delay |
|---|---|
| 1 | Immediate |
| 2 | 30 seconds |
| 3 | 2 minutes |
| 4 | 5 minutes |
| 5 | 5 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.
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:
POST. Copy the production URL and register it in the Agent Media Tools webhook UI.event is pipeline.completed or pipeline.failedBecause 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.
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.
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.
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.
Here's a complete end-to-end example using the tools mentioned in this post:
pipeline.completed and pipeline.failedNo polling. No manual checks. Just push-based delivery from start to finish.
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