A Slack alert on batch.completed means nobody has to keep refreshing the Scriptube dashboard to know when a large batch is done. This needs two things: a Slack incoming webhook, and something in the middle that receives Scriptube's webhook, checks it's genuine, and reposts the parts that matter into Slack.
Create the Slack incoming webhook
In Slack, add an Incoming Webhooks app to the channel you want alerts in (via your workspace's App Directory, or a Slack app you already manage). You'll get a URL like https://hooks.slack.com/services/T000/B000/XXXXXXXX â a POST to this URL with {"text": "..."} posts a message. Keep this URL private; anyone with it can post to the channel.
The middle step: n8n (easiest path)
If you're already using the official node, this is the shortest route â see n8n-youtube-transcript-automation for installing it first.
[Scriptube Trigger: Batch Completed] â [Slack: Send Message]
- Add a Scriptube Trigger node, Event = Batch Completed, and activate the workflow. n8n registers the webhook and verifies every delivery's signature for you â see that guide for how the Replay Window and Webhook Secret fit together.
- Add n8n's native Slack node, operation Send Message, pointed at your Slack credential and channel.
- Build the message text from the trigger payload:
Batch {{$json.data.batch_id}} finished: {{$json.data.status}}
{{$json.data.completed_count}} completed, {{$json.data.failed_count}} failed, {{$json.data.item_count}} total
Branch on {{$json.data.status}} with an IF node if you want a different message (or a different channel) for a fully failed batch versus a completed one â status is completed, failed, or partial (some items succeeded, some didn't).
Without n8n: a plain relay
If you're not on n8n, the same shape works with any small HTTP service (a Cloudflare Worker, a Lambda, a one-route Flask app) sitting between Scriptube and Slack:
import hashlib
import hmac
import time
import requests
from flask import Flask, request, abort
app = Flask(__name__)
SECRET = "a-dedicated-random-string-16-to-256-chars"
SLACK_WEBHOOK = "https://hooks.slack.com/services/T000/B000/XXXXXXXX"
@app.post("/scriptube-webhook")
def handle():
timestamp = request.headers.get("X-Scriptube-Timestamp", "")
signature = request.headers.get("X-Scriptube-Signature", "")
raw_body = request.get_data() # raw bytes, not request.json
if abs(time.time() - float(timestamp or 0)) > 300:
abort(401, "stale timestamp")
expected = "sha256=" + hmac.new(
SECRET.encode(), f"{timestamp}.".encode() + raw_body, hashlib.sha256
).hexdigest()
if not hmac.compare_digest(expected, signature):
abort(401, "bad signature")
payload = request.get_json()
data = payload.get("data", {})
text = (
f"Batch {data.get('batch_id')} finished: {data.get('status')}\n"
f"{data.get('completed_count')} completed, "
f"{data.get('failed_count')} failed, "
f"{data.get('item_count')} total"
)
requests.post(SLACK_WEBHOOK, json={"text": text}, timeout=5)
return {"ok": True}
Deploy it anywhere reachable over HTTPS, then register it:
curl -X POST https://scriptube.me/api/webhooks/register \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-relay.example.com/scriptube-webhook",
"events": ["batch.completed"],
"secret": "a-dedicated-random-string-16-to-256-chars"
}'
Use hmac.compare_digest rather than == â a plain string comparison leaks timing information about how many leading bytes matched, which is the kind of detail an HMAC check exists specifically to avoid.
Test it before you wait for a real batch
You don't have to submit a real batch and wait for it to finish just to check your Slack formatting works. Once a webhook is registered, queue a test delivery against it directly:
curl -X POST https://scriptube.me/api/webhooks/{webhook_id}/test \
-H "X-API-Key: YOUR_API_KEY"
This returns 202 immediately and queues a synthetic event ({"event": "test", "data": {"message": "This is a test event from Scriptube", "webhook_id": "..."}}) for delivery to your registered URL â signed exactly like a real one, so it's a genuine end-to-end check of your signature verification, not just your Slack formatting. Check GET /api/webhooks/{webhook_id}/logs afterward to confirm it was delivered and see the response code your receiver returned.
Alerting on failures, not just completions
Subscribe to batch.failed too if you want a distinct, louder alert for a batch that failed entirely (as opposed to partial, where some items succeeded):
"events": ["batch.completed", "batch.failed"]
Both land on the same webhook URL â check the top-level event field in the payload to tell them apart, and route to a different Slack channel or a @here mention for failures. If a batch is only partially failing and you want to recover without resubmitting the whole thing, retry-failed-youtube-transcripts covers retrying just the failed items.
What to do when deliveries stop arriving
A webhook that fails ten consecutive deliveries gets deactivated automatically â if your relay goes down for a while, Scriptube stops trying rather than retrying forever. Check GET /api/webhooks/{webhook_id} for is_active and failure_count if alerts suddenly stop, and re-register once your receiver is back up.
If a delivery did fail and you want to replay that specific one instead of waiting for the retry ladder, GET /api/webhooks/{webhook_id}/logs lists every delivery with its delivery_id and status, and POST /api/webhooks/{webhook_id}/retry?delivery_id=... queues it again immediately. A delivery that's already burned through every attempt on the ladder returns a 409 instead of re-queuing â at that point, fix the receiver and wait for the next real event rather than trying to force the old one through.
Picking plain text vs. a formatted message
The examples above send a plain-text Slack message, which is the fastest thing to get working and perfectly fine for a low-traffic channel. If this alert is going into a busy channel, consider Slack's Block Kit format instead â a colored sidebar (green for completed, red for failed) makes the status scannable without reading the text, which matters once there are a dozen other messages above it by the time someone looks.
For the full event list and payload shapes this is all built on, see n8n-youtube-transcript-automation or /docs.