# Webhook

So erfährt dein Tool, welcher Knopf gedrückt wurde, und prüft, dass die Anfrage echt ist.

Über den Webhook erfährt dein Tool, dass du in der App auf einen Aktions-Knopf getippt hast.

## Einrichten

Die Webhook-URL trägst du beim Anlegen des Packlets oder später in seinen Einstellungen ein.

- Sie muss mit `https://` beginnen und öffentlich erreichbar sein.
- Zugangsdaten in der Adresse (`https://name:passwort@…`) sind nicht erlaubt.

## Die Anfrage

Bei einem Tipp schickt Samy einen `POST` mit diesem JSON-Body:

```json
{
  "message_id": "5b0c1a52-…",
  "packlet_id": "9d2f7c10-…",
  "button_id": "label",
  "pressed_at": "2026-11-09T18:00:00Z"
}
```

| Feld | Bedeutung |
|---|---|
| `message_id` | Die Nachricht, zu der der Knopf gehört. Derselbe Wert wie in der Antwort beim Senden. |
| `packlet_id` | Das Packlet. |
| `button_id` | Die `id`, die du dem Knopf gegeben hast. |
| `pressed_at` | Zeitpunkt des Tipps, ISO 8601 in UTC. |

| Header | Wert |
|---|---|
| `Content-Type` | `application/json` |
| `X-Samy-Event` | `button.pressed` |
| `X-Samy-Signature` | `sha256=<Signatur>` |
| `User-Agent` | `Samy-Webhook/1.0` |

## Antworten und Wiederholungen

- Antworte innerhalb von 10 Sekunden mit einem Status 2xx. Der Inhalt der Antwort wird nicht gelesen.
- Bei Zeitüberschreitung, Status `429` oder einem Status ab 500 versucht Samy es bis zu fünfmal erneut, mit wachsendem Abstand.
- Andere Fehler (zum Beispiel 400 oder 404) werden nicht wiederholt.
- Weiterleitungen folgt Samy nicht.

Wegen der Wiederholungen kann dieselbe Anfrage mehrfach ankommen. Merke dir `message_id` zusammen mit `button_id`, wenn eine Aktion nur einmal laufen darf.

## Signatur prüfen

Mit der Signatur stellst du sicher, dass die Anfrage wirklich von Samy kommt.

- Algorithmus: HMAC-SHA256 über den **unveränderten** Body (die rohen Bytes, nicht das neu erzeugte JSON).
- Geheimnis: der SHA-256-Hash deines Schlüssels als Hex-Text in Kleinbuchstaben.
- Format des Headers: `sha256=` gefolgt vom HMAC als Hex-Text.

```python
import hashlib, hmac, os

SAMY_KEY = os.environ["SAMY_KEY"]

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

```javascript
import { createHash, createHmac, timingSafeEqual } from "node:crypto";

export function isFromSamy(rawBody, signature = "") {
  const secret = createHash("sha256").update(process.env.SAMY_KEY).digest("hex");
  const expected = "sha256=" + createHmac("sha256", secret).update(rawBody, "utf8").digest("hex");
  const a = Buffer.from(expected);
  const b = Buffer.from(signature);
  return a.length === b.length && timingSafeEqual(a, b);
}
```

Erzeugst du in der App einen neuen Schlüssel, ändert sich auch das Geheimnis für die Signatur.
