Auf Eingaben reagieren

Webhook einrichten

So erfährt dein Tool, was du in der App tust: Empfänger bauen, Signatur prüfen, testen.

Als Markdown öffnen

Bisher hat nur dein Tool mit Samy gesprochen. Mit einem Webhook geht es auch zurück: Tippst du einen Knopf, legst einen Schalter um oder schreibst im Chat, ruft Samy dein Tool auf.

In der AppDutippst, schaltest oder schreibst
EreignisAnzeige
DazwischenSamyspeichert und schickt das Ereignis weiter
POST, signiert/v1/push
Bei dirDein Toolprüft die Signatur und reagiert

Am Ende dieser Seite hast du einen kleinen Empfänger, der Ereignisse annimmt, ihre Echtheit prüft und darauf reagiert. Auf ihm bauen Knöpfe, Elemente in Nachrichten und Antworten auf.

Du brauchst: ein Packlet mit Schlüssel und einen Ort, an dem dein Tool unter einer öffentlichen https-Adresse erreichbar ist.

1. Einen Empfänger bauen

Ein Webhook-Empfänger ist ein kleiner Webserver mit einer einzigen Adresse, die POST-Anfragen annimmt. Dieser hier gibt jedes Ereignis aus:

# pip install flask
from flask import Flask, request

app = Flask(__name__)

@app.post("/samy")
def samy_webhook():
    event = request.get_json()
    print(event)
    return "", 204

app.run(port=8080)

2. Öffentlich erreichbar machen

Samy muss deinen Empfänger aus dem Internet erreichen. Die Adresse muss mit https:// beginnen. Adressen wie localhost oder 192.168.… lehnt Samy ab.

  • Auf einem Server mit eigener Domain ist das schon so.
  • Auf deinem Rechner hilft zum Testen ein Tunnel, zum Beispiel cloudflared tunnel --url http://localhost:8080 oder ngrok http 8080. Beide geben dir eine öffentliche https-Adresse, die auf deinen Rechner zeigt.

3. Die Adresse in Samy eintragen

Die Webhook-URL trägst du beim Anlegen des Packlets ein oder später in seinen Einstellungen: im Kanal oben auf Avatar oder Namen tippen. Im Beispiel wäre das https://deine-adresse.example/samy.

Zum Testen schickst du dir eine Nachricht mit einem Aktions-Knopf und tippst ihn an:

Shell
curl https://api.packlets.app/v1/push \
  -H "Authorization: Bearer $SAMY_KEY" \
  -H "Content-Type: application/json" \
  -d '{"text": "Kommt der Webhook an?", "buttons": [{"id": "ja", "label": "Ja"}]}'

Dein Empfänger gibt jetzt das Ereignis aus:

JSON
{
  "type": "button",
  "packlet_id": "9d2f7c10-3a5b-4c8e-9f21-6b7d8e9f0a12",
  "message_id": "5b0c1a52-7c1e-4a43-9d0b-2f6f0c7d1e11",
  "button_id": "ja",
  "pressed_at": "2026-11-09T18:00:00.000Z"
}

Kommt nichts an? Im Protokoll des Packlets siehst du jeden Versuch mit dem Status deiner Antwort. Mehr unter Fehlersuche.

4. Signatur prüfen

Deine Adresse ist öffentlich. Jeder, der sie kennt, könnte dir gefälschte Ereignisse schicken. Deshalb unterschreibt Samy jede Anfrage im Header X-Samy-Signature, und dein Tool prüft die Unterschrift, bevor es etwas tut.

So wird gerechnet:

  1. Das Geheimnis ist der SHA-256-Hash deines Schlüssels, als Hex-Text.
  2. Mit diesem Geheimnis bildest du den HMAC-SHA256 über den Body, genau so, wie er angekommen ist.
  3. Das Ergebnis als Hex-Text, mit sha256= davor, muss dem Header gleichen.
import hashlib, hmac, os
from flask import Flask, request

app = Flask(__name__)
SECRET = hashlib.sha256(os.environ["SAMY_KEY"].encode()).hexdigest().encode()

def is_from_samy(raw_body: bytes, signature: str) -> bool:
    expected = "sha256=" + hmac.new(SECRET, raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature or "")

@app.post("/samy")
def samy_webhook():
    if not is_from_samy(request.get_data(), request.headers.get("X-Samy-Signature", "")):
        return "", 401
    event = request.get_json()
    print(event)
    return "", 204

app.run(port=8080)

Achtung: Rechne über den Body, wie er angekommen ist, nicht über das JSON, das du daraus neu erzeugst. Schon ein anderes Leerzeichen ändert die Signatur.

Erzeugst du in der App einen neuen Schlüssel, ändert sich auch das Geheimnis. Dein Tool braucht dann den neuen Schlüssel.

Löst dein Tool etwas aus, das nicht doppelt passieren darf, prüf zusätzlich timestamp und event_id aus dem Body: Schutz vor Wiederholung.

5. Auf Ereignisse reagieren

Es gibt drei Ereignisse. Das Feld type sagt, welches es ist:

typeWannWichtige Felder
buttonDu tippst einen Aktions-Knopf.message_id, button_id
uiDu bedienst ein Element in einer Nachricht.message_id, element_id, value
messageDu schreibst im Chat.text

Alle Felder stehen in der Referenz Webhook-Ereignisse. Ersetz im Empfänger die Ausgabe durch deine Logik:

    event = request.get_json()
    if event["type"] == "button" and event["button_id"] == "restart":
        starte_neu(event["message_id"])      # deine Funktion
    elif event["type"] == "ui" and event["element_id"] == "rollback":
        rollback()                           # deine Funktion
    elif event["type"] == "message":
        antworte(event["text"])              # deine Funktion
    return "", 204

Ereignisse, die dein Tool nicht kennt, ignoriert es am besten. So läuft es weiter, wenn Samy neue bekommt.

Schnell antworten

Samy wartet höchstens 10 Sekunden auf deine Antwort. Jeder Status 2xx gilt als angekommen, der Inhalt deiner Antwort wird nicht gelesen.

Dauert die Arbeit länger, zum Beispiel eine KI-Antwort, dann bestätige sofort und arbeite danach. Das Ergebnis schickst du mit POST /v1/push, sobald es fertig ist.

Doppelte Ereignisse

Antwortet dein Tool nicht, zu spät oder mit einem Serverfehler, versucht Samy es bis zu fünfmal erneut, über etwa zehn Minuten verteilt. Dasselbe Ereignis kann also mehrfach ankommen, auch wenn dein Tool es beim ersten Mal schon verarbeitet hat.

Darf eine Aktion nur einmal laufen, merk dir, was du schon gesehen hast:

EreignisEindeutig durch
buttonmessage_id und button_id
messagemessage_id
uimessage_id, element_id und changed_at

Bei Schaltern ist das meist egal: Denselben Wert zweimal zu setzen schadet nicht. Ein Neustart oder eine Zahlung dagegen darf nur einmal laufen.

Wie es weitergeht