
# keenauth_webhook_endpoint

The `keenauth_webhook_endpoint` resource manages a webhook endpoint: an HTTPS address that receives the environment's events, signed with Standard Webhooks. The provider keeps the signing secret in state only encrypted to an RSA public key that you give it.

```terraform
resource "keenauth_webhook_endpoint" "orders" {
  key         = "orders"
  url         = "https://hooks.example.com/keenauth"
  description = "Order service"
  event_types = ["user.created", "user.updated", "organization.created"]
  event_schema_versions = {
    "user.created" = 1
  }
  public_key = file("${path.module}/webhook-secret-key.pub.pem")

  rotation_trigger          = time_rotating.webhook_secret.id
  previous_secret_ttl_hours = 48
}

resource "time_rotating" "webhook_secret" {
  rotation_days = 90
}

output "orders_webhook_secret_encrypted" {
  value = keenauth_webhook_endpoint.orders.encrypted_signing_secret
}
```

## Arguments

- `url` is required: `https` only, no port or port 443, no user information, no fragment, and a host name rather than an IP address (at most 2048 characters).
- `key` is unique in the environment (`^[a-z0-9][a-z0-9_-]{0,63}$`). If you leave it out, the API derives `endpoint-<last 8 characters of the ID>`.
- `event_types` lists catalog event types (at most 100). An empty set, the default, means every type except the high-volume `authentication.*` types, which you must name.
- `event_schema_versions` maps an event type to a payload schema version. Each type must be one the endpoint receives.
- `payload_mode` is `thin` (the default: IDs, changed field names and status fields, no personal data) or `full`.
- `rate_limit_per_second` is 1 to 1000; the default is 50.
- `status` is `enabled` (the default) or `disabled_by_user`.

Every argument changes in place. Updates send only the changed fields as a JSON Merge Patch, with the ETag of the last read as `If-Match`. If the endpoint was changed elsewhere after the last read, the apply fails with `412` and nothing is changed; run plan again to review the drift.

## The signing secret is never stored in plain text

The API returns the signing secret (`idp_whsec_…`) only when the endpoint is created and when the secret is rotated. With `public_key`, the provider encrypts the secret with RSA-OAEP (SHA-256, MGF1 SHA-256, empty label) and writes only the base64 ciphertext to `encrypted_signing_secret`. Without `public_key` the secret is discarded; rotate it later with `keenauth webhook-endpoints rotate-secret` or set `public_key`. The plain secret never reaches state, a plan file, an output or a log.

Create the key pair once, outside OpenTofu, and keep the private key out of your configuration:

```shell
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:3072 -out webhook-secret-key.pem
openssl pkey -in webhook-secret-key.pem -pubout -out webhook-secret-key.pub.pem
```

After `tofu apply`, decrypt the secret and give it to the receiver:

```shell
tofu output -raw orders_webhook_secret_encrypted | base64 -d | \
  openssl pkeyutl -decrypt -inkey webhook-secret-key.pem \
    -pkeyopt rsa_padding_mode:oaep -pkeyopt rsa_oaep_md:sha256 -pkeyopt rsa_mgf1_md:sha256
```

`public_key` must be a PEM-encoded RSA public key (`PUBLIC KEY` or `RSA PUBLIC KEY`) of at least 2048 bits. A private key or a smaller key is rejected at plan time.

## Rotation

The secret is rotated when you set or change `public_key`, and when `rotation_trigger` changes to a new value, for example the ID of a `time_rotating` resource. After a rotation the old secret keeps signing next to the new one for `previous_secret_ttl_hours` (0 to 168; the API default is 24; 0 drops it at once), so the receiver can switch without missing events. `previous_secret_expires_at` shows when the old secret stops. Removing `public_key` only removes `encrypted_signing_secret`; it does not rotate the secret.

## Limits and status

An enabled endpoint counts against the plan's limit per environment: Free 3, Startup 20, Business 20, Enterprise 50. Creating or enabling one more fails with `403 quota_exceeded`; disable or delete another endpoint, or upgrade the plan.

KeenAuth disables an endpoint that keeps failing (`disabled_by_failure`) or whose receiver answers `410 Gone` (`disabled_by_receiver`). The next plan shows the change back to the configured `status`, and applying it enables the endpoint again.

## Import

Import with the endpoint ID or its key:

```shell
tofu import keenauth_webhook_endpoint.orders orders
```

The signing secret cannot be read back, so `encrypted_signing_secret` is empty after import. If your configuration sets `public_key`, the next apply rotates the secret and delivers the new one encrypted.

Test sends, replays and delivery retries are one-off actions, not configuration. Use `keenauth webhook-endpoints test`, `replay` and `deliveries` for them.
