finlight Logo

Tuesday, October 20, 2026

Push News to Slack or Discord with finlight Webhooks

Kevin Bartsch

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

Get your free API key →