> ## Documentation Index
> Fetch the complete documentation index at: https://docs.getovra.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhooks

> Signierte HTTPS-Notifications für ~50 Event-Typen, mit plan-tier-abhängigen Retry-Policies.

Webhooks sind wie Ovra deinen Systemen mitteilt dass etwas passiert ist. Jede state-ändernde Operation feuert einen. Deliveries sind HMAC-SHA256-signiert, retried auf einem plan-tier-aware Backoff-Schedule und SSRF-checked gegen Private-IPs und Metadata-Endpoints.

## Subscriben

```bash theme={}
curl -X POST https://api.getovra.com/webhooks \
  -H "Authorization: Bearer $OVRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-server.com/ovra/webhook",
    "events": ["transaction.completed", "intent.approved", "claim.request.paid"],
    "description": "Production-Webhook"
  }'
```

`["*"]` für alle Events.

URL-Constraints (`isBlockedWebhookUrl`):

* HTTPS oder HTTP (HTTPS in Prod stark empfohlen)
* Kein localhost
* Kein RFC 1918 (10.0.0.0/8, 172.16/12, 192.168/16)
* Kein Link-local (169.254.0.0/16) oder Metadata-Endpoints (`169.254.169.254`, `metadata.google.internal`)
* DNS-Rebinding-Check zur Delivery-Time

## Event-Katalog

\~50 Event-Typen über diese Kategorien. Source of Truth: `apps/api/src/services/webhook.ts` `WebhookEvent`-Union.

<AccordionGroup>
  <Accordion title="Intent">
    `intent.created` · `intent.approved` · `intent.denied` · `intent.expired` · `intent.cancelled` · `intent.matched` (reserviert) · `intent.mismatched` (reserviert)
  </Accordion>

  <Accordion title="Transaction">
    `transaction.created` · `transaction.updated` · `transaction.authorization` · `transaction.settlement` · `transaction.settled` · `transaction.completed` · `transaction.declined` · `transaction.refunded`
  </Accordion>

  <Accordion title="Card">
    `card.issued` · `card.activated` · `card.frozen` · `card.unfrozen` · `card.closed` · `card.rotated` · `card.funded` · `card.shipped` · `card.limits_changed` · `card.details_changed`
  </Accordion>

  <Accordion title="Card-Limit-Request">
    `card_limit_request.updated`
  </Accordion>

  <Accordion title="Wallet">
    `wallet.created` · `wallet.funded` · `wallet.credited`
  </Accordion>

  <Accordion title="Transfer">
    `transfer.completed` · `transfer.failed` · `transfer.split.completed`
  </Accordion>

  <Accordion title="Risk">
    `risk.agent_frozen` · `risk.review_required` · `risk.alert_created` · `risk.denied` · `risk.velocity_alert` · `risk.geo_impossible`
  </Accordion>

  <Accordion title="Agent">
    `agent.created` · `agent.frozen` · `agent.unfrozen`
  </Accordion>

  <Accordion title="Einzug (Legacy `claim.*` Namespace)">
    `claim.request.created` · `claim.request.paid` · `claim.request.expired` · `claim.request.cancelled`
  </Accordion>

  <Accordion title="Dispute">
    `dispute.created` · `dispute.updated` · `dispute.resolved`
  </Accordion>

  <Accordion title="MPP (Credential-Issuer)">
    `mpp.credential.minted` · `mpp.credential.consumed` · `mpp.credential.expired` (reserviert) · `mpp.transaction.completed`
  </Accordion>

  <Accordion title="Verification + Misc">
    `verification.mismatch` · `mobile_wallet.updated` · `payment.updated` · `receipt.updated` · `bill.updated` · `cardholder.updated`
  </Accordion>
</AccordionGroup>

## Delivery-Format

Headers:

| Header               | Wert                                                                |
| -------------------- | ------------------------------------------------------------------- |
| `X-Ovra-Event`       | Event-Name                                                          |
| `X-Ovra-Timestamp`   | Unix-Sekunden zur Sign-Time                                         |
| `X-Ovra-Delivery-Id` | Eindeutig pro Versuch                                               |
| `X-Ovra-Signature`   | `sha256=<hex>` HMAC-SHA256 von `{deliveryId}.{timestamp}.{rawBody}` |
| `X-Request-Id`       | Korrelation zurück zum Originating-Request                          |

Body:

```json theme={}
{
  "event": "transaction.completed",
  "createdAt": "2026-04-20T12:34:56.789Z",
  "data": {
    "transactionId": "tx_...",
    "agentId": "ag_...",
    "intentId": "int_...",
    "amountEuros": 4.51,
    "merchant": "hetzner.com",
    "status": "completed"
  }
}
```

## Signatur verifizieren

<CodeGroup>
  ```ts Node theme={}
  import { createHmac, timingSafeEqual } from "node:crypto";

  function verify(req: { headers: Record<string,string>, rawBody: Buffer }, secret: string) {
    const sig = req.headers["x-ovra-signature"] ?? "";
    const ts  = req.headers["x-ovra-timestamp"] ?? "";
    const id  = req.headers["x-ovra-delivery-id"] ?? "";
    const expected = "sha256=" + createHmac("sha256", secret)
      .update(`${id}.${ts}.${req.rawBody.toString("utf8")}`)
      .digest("hex");
    return timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
  }
  ```

  ```python Python theme={}
  import hmac, hashlib
  def verify(headers, raw_body, secret):
      sig = headers.get("x-ovra-signature", "")
      ts  = headers.get("x-ovra-timestamp", "")
      did = headers.get("x-ovra-delivery-id", "")
      expected = "sha256=" + hmac.new(secret.encode(),
          f"{did}.{ts}.{raw_body.decode()}".encode(),
          hashlib.sha256).hexdigest()
      return hmac.compare_digest(sig, expected)
  ```
</CodeGroup>

## Retry-Policy (plan-tier-aware)

| Tier         | Max Versuche | Backoff                 | Dead-Letter |
| ------------ | ------------ | ----------------------- | ----------- |
| `basic`      | 1            | –                       | –           |
| `full`       | 1            | –                       | –           |
| `full_retry` | 5            | 1m · 5m · 15m · 1h · 4h | –           |
| `full_dlq`   | 5            | Wie oben                | ✓           |

Background-Poller läuft alle 30s, klaut Rows via `FOR UPDATE SKIP LOCKED` damit Multi-Instance-API-Hosts nie doppelt deliveren.

| Plan       | Webhook-Tier |
| ---------- | ------------ |
| Free       | basic        |
| Starter    | full         |
| Business   | full\_retry  |
| Enterprise | full\_dlq    |

## Best Practices

* **Signaturen verifizieren** bevor verarbeitet wird — niemals dem Body unsigned vertrauen.
* **Idempotente Handler** — derselbe Event kann mehrfach delivered werden.
* **Schnell acknowledgen** — 2xx in 30s zurückgeben. Slow Work in Queue verschieben.
* **Timestamp-Window prüfen** um sehr alte Replays abzulehnen.

## Weiter

<CardGroup cols={2}>
  <Card title="Intelligenz" icon="chart-line" href="/de/concepts/intelligence">
    Audit + Decision-Logs ergänzen Webhooks für Forensik.
  </Card>

  <Card title="Sandbox" icon="flask" href="/de/concepts/sandbox">
    Signaturverifikation End-to-End testen.
  </Card>
</CardGroup>
