Webhook recipes
A webhook posts JSON to a URL when an event happens. With a payload template you choose the body, so the same webhook can talk to Slack, Discord or any service that takes a JSON POST.
Create a webhook
In the Console, open Webhooks → New webhook, or use the API:
curl -X POST "https://lantfly.com/api/v3/webhooks" \
-H "Authorization: Bearer $LANTFLY_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"description": "Scraper finished",
"event_types": ["ACTOR.RUN.SUCCEEDED", "ACTOR.RUN.FAILED"],
"condition": { "actor_id": "<actorId>" },
"request_url": "https://example.com/lantfly-hook"
}'
| Field | Meaning |
|---|---|
event_types | One or more events (see Integrations). |
condition | actor_id and/or task_id for run events. Empty: every run of the account. |
request_url | An http or https URL on a public address. Private addresses are refused, and redirects are not followed. |
payload_template | Optional: the body to send, as a string (below). |
headers | Optional extra headers, for example an Authorization header your server expects. |
description | A name for the webhook. |
is_enabled | Turn it off without deleting it. |
The answer contains the signing secret, once. Keep it if your server verifies deliveries.
Then click Test (or POST /api/v3/webhooks/{id}/test): Lantfly sends your latest run as a sample, once, and shows the answer.
How deliveries work
POSTwithContent-Type: application/json, a 30-second timeout and the headersX-Lantfly-Event,X-Lantfly-Webhook-Dispatch-IdandX-Lantfly-Signature.- A
2xxanswer is a success. Anything else, or a network error, is retried after 1, 5, 25, 125 and 625 seconds (6 attempts in all). - Each webhook shows its last delivery and failed deliveries. Redeliver a failed one with
POST /api/v3/webhooks/{id}/dispatches/{dispatch}/redeliver.
Payload templates
Without a template, the body is the default payload:
{
"user_id": "…",
"created_at": "2026-11-03T08:00:00.000Z",
"event_type": "ACTOR.RUN.SUCCEEDED",
"event_data": { "actor_id": "…", "task_id": null, "run_id": "…" },
"resource": {
"id": "…",
"actor_slug": "lantfly~web-scraper",
"status": "SUCCEEDED",
"status_message": "ok: 42 items from 3 targets",
"default_dataset_id": "…",
"default_key_value_store_id": "…",
"stats": { "run_time_secs": 31, "result_count": 42 },
"usage_total_usd": 0.021,
"started_at": "…",
"finished_at": "…"
}
}
A template is a JSON text with {{path}} placeholders into that payload, for example {{resource.default_dataset_id}} or {{event_data.run_id}}.
Each placeholder is replaced by the JSON value at that path: a string comes with its quotes, a number without, a missing value as null. Write placeholders without quotes around them, and do not put them inside a longer string:
- Right:
{"run": {{resource.id}}, "items": {{resource.stats.result_count}}} - Wrong:
{"run": "{{resource.id}}"}and{"text": "Run {{resource.id}} finished"}
When the filled template is not valid JSON, Lantfly sends {"payload": "<the filled text>"} instead.
Listening events have their own payload: event_data has project_id and mention_id (alerts: alert_id, rule_id, mention_id), and resource is the mention (platform, url, text, author_name, author_handle, published_at, views, likes, comments, shares, engagement, sentiment, hashtags…) or the alert event, plus project_id and project.
Slack
Incoming webhook
-
In Slack, create an app with Incoming Webhooks and add a webhook to a channel. Copy its
https://hooks.slack.com/services/…URL. -
Create a Lantfly webhook with that URL and a template that fills Slack's
text:payload_template: run finished{"text": {{resource.status_message}}}payload_template: new mention{"text": {{resource.url}}}
The status message is a short summary such as ok: 42 items from 3 targets. Actors built with the Lantfly SDKs always set one at the end; other actors may leave it empty, and Slack refuses an empty text.
Workflow Builder
For a formatted message, create a Slack workflow that starts from a webhook and declare variables (actor, status, summary, dataset). Use them in the workflow's message step, and send them from Lantfly:
{
"actor": {{resource.actor_slug}},
"status": {{resource.status}},
"summary": {{resource.status_message}},
"dataset": {{resource.default_dataset_id}}
}
Discord
-
In Discord: Server settings → Integrations → Webhooks → New webhook, pick a channel and copy the URL.
-
Use a template that fills Discord's
content:payload_template: run finished{"content": {{resource.status_message}}, "username": "Lantfly"}payload_template: new mention{"content": {{resource.url}}, "username": "Lantfly"}
Discord shows a preview for mention URLs it can unfurl.
Your own server
Keep the default payload and verify the signature before you trust the body. X-Lantfly-Signature is sha256= followed by the hex HMAC-SHA256 of the raw body with the webhook's secret.
import crypto from 'node:crypto';
import express from 'express';
const app = express();
const secret = process.env.LANTFLY_WEBHOOK_SECRET;
app.post('/lantfly-hook', express.raw({ type: 'application/json' }), (req, res) => {
const expected = 'sha256=' + crypto.createHmac('sha256', secret).update(req.body).digest('hex');
const given = req.get('X-Lantfly-Signature') ?? '';
const ok = given.length === expected.length && crypto.timingSafeEqual(Buffer.from(given), Buffer.from(expected));
if (!ok) return res.status(401).end();
const event = JSON.parse(req.body.toString('utf8'));
res.status(200).end(); // answer fast, work after
if (event.event_type === 'ACTOR.RUN.SUCCEEDED') {
queueDownload(event.resource.default_dataset_id);
}
});
app.listen(3000);
import hashlib
import hmac
import os
from flask import Flask, abort, request
app = Flask(__name__)
SECRET = os.environ["LANTFLY_WEBHOOK_SECRET"].encode()
@app.post("/lantfly-hook")
def lantfly_hook():
body = request.get_data()
expected = "sha256=" + hmac.new(SECRET, body, hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected, request.headers.get("X-Lantfly-Signature", "")):
abort(401)
event = request.get_json()
# e.g. fetch the items of event["resource"]["default_dataset_id"]
return "", 200
Tips:
- Answer within 30 seconds, ideally at once, and do the work afterwards. A slow answer counts as a failure and is retried.
- Deduplicate with
X-Lantfly-Webhook-Dispatch-Id: a retry of the same delivery has the same id. - Rotate the secret with
POST /api/v3/webhooks/{id}/rotate-secretif it leaks.
More on signatures: Webhook signatures.
Webhooks for one run
You can also attach up to 10 webhooks to a single run when you start it, with webhooks in the run request. They fire only for that run. See Runs.