Tuesday, October 20, 2026
Push News to Slack or Discord with finlight Webhooks
Push News to Slack or Discord with finlight Webhooks
The fastest way to get news in front of your team is to push it into the chat they already watch. With finlight webhooks, you don't poll: finlight calls your endpoint the moment an article matches your filters. This tutorial sets up a small Python endpoint that verifies the webhook and forwards it to Slack or Discord.
How finlight webhooks work
You create a webhook in the dashboard, set its filters (tickers, categories, and so on), and give finlight your endpoint URL. When an article matches, finlight POSTs the article to your endpoint and signs the request, so you can confirm it really came from finlight. Each request carries two headers:
X-Webhook-Signature— an HMAC-SHA256 signature of the body.X-Webhook-Timestamp— used to reject replays (a 5-minute tolerance window).
The official SDK verifies both for you.
The endpoint
import os
import requests
from flask import Flask, request
from finlight_client import WebhookService, WebhookVerificationError
app = Flask(__name__)
WEBHOOK_SECRET = os.environ["FINLIGHT_WEBHOOK_SECRET"]
CHAT_WEBHOOK_URL = os.environ["CHAT_WEBHOOK_URL"]
@app.route("/finlight-webhook", methods=["POST"])
def finlight_webhook():
raw_body = request.get_data(as_text=True)
signature = request.headers.get("X-Webhook-Signature")
timestamp = request.headers.get("X-Webhook-Timestamp")
try:
article = WebhookService.construct_event(
raw_body, signature, WEBHOOK_SECRET, timestamp
)
except WebhookVerificationError:
return "", 400
text = (
f"*{article.title}*\n"
f"{article.sentiment} · {article.source}\n"
f"{article.link}"
)
# Slack incoming webhook expects {"text": ...}
requests.post(CHAT_WEBHOOK_URL, json={"text": text})
return "", 200
WebhookService.construct_event(...) checks the signature, validates the timestamp, and returns a
parsed Article. If anything fails it raises WebhookVerificationError, and you return a 400.
Sending to Discord instead
Discord incoming webhooks use a content field rather than text. Swap the post:
requests.post(CHAT_WEBHOOK_URL, json={"content": text})
Everything else stays the same.
Always verify
Verification matters: without it, anyone who finds your URL could post fake articles into your channel.
Always pass the raw, unparsed body to construct_event (don't re-serialize it first), keep your
endpoint secret in an environment variable, and return quickly so finlight isn't kept waiting.
Enriched vs raw
finlight offers enriched webhooks (with sentiment and entities) and raw webhooks (lightweight, faster). Use enriched when your alert should show sentiment or the companies involved, raw when you just need the headline as fast as possible.
Where to go next
- REST vs WebSocket vs Webhooks vs MCP to see when webhooks beat streaming.
- Full documentation for webhook setup, payloads, and testing.
