---
id: "webhook-without-signature-or-idempotency"
type: "rule"
severity: "error"
url: "https://localhost:3000/catalogue/rules/webhook-without-signature-or-idempotency"
---

# Webhook without signature verification or idempotency

A webhook endpoint accepts callbacks without verifying the caller's signature or without deduplicating retried events.

| | |
|---|---|
| Severity | error |
| Selects | every node of kind webhook-endpoint |
| Components in scope | 4 |

## Why

A webhook is a public endpoint that its caller retries. Without signature verification anyone can post a fake event; without idempotency the provider's own retries apply the same event twice.

## What it checks

It looks at every node of kind webhook-endpoint, and fires when:

- any of these hold:
  - the node's `signatureVerified` is false
  - the node's `idempotent` is false

Message template:

```
Webhook '{{node.id}}' does not verify signatures or is not idempotent. Verify the caller's signature and deduplicate by event id.
```

## Applies to

- [Webhook endpoint](https://localhost:3000/catalogue/kinds/webhook-endpoint.md) `webhook-endpoint`

4 catalogue components fall under it.

## Tripped by

Reference designs whose broken twin names this rule in its header:

- [gojek-platform](https://localhost:3000/d/zeema/gojek-platform)
- [metrics-monitoring](https://localhost:3000/d/zeema/metrics-monitoring)
- [payment-system](https://localhost:3000/d/zeema/payment-system)
- [stripe-payments](https://localhost:3000/d/zeema/stripe-payments)
- [twitch-live](https://localhost:3000/d/zeema/twitch-live)

## Source (`rule` YAML, verbatim)

```yaml
# yaml-language-server: $schema=../schema/rule.schema.json
# A webhook is a public endpoint that the caller retries. Without signature verification
# anyone can post a fake "payment succeeded"; without idempotency the provider's own
# retries apply the same event twice.
# Tripped by: designs/payment-system.broken.yaml
schemaVersion: "1"
type: "rule"
id: "webhook-without-signature-or-idempotency"
severity: "error"
select:
  node:
    kind: "webhook-endpoint"
when:
  any:
    - prop: "signatureVerified"
      equals: false
    - prop: "idempotent"
      equals: false
message: "Webhook '{{node.id}}' does not verify signatures or is not idempotent. Verify the caller's signature and deduplicate by event id."
```

Human page: https://localhost:3000/catalogue/rules/webhook-without-signature-or-idempotency
