Webhook module

Delivers a certificate payload to any HTTP endpoint — the escape hatch for targets that don't fit one of the other modules. You configure the URL, headers, payload template, timeout, and retry count; the receiving system owns persistence and rollback.

01Overview

  • Method: HTTP POST.
  • Auth: up to the operator — any combination of HTTP headers (bearer tokens, HMAC signatures, Basic, custom).
  • Body: default JSON payload, or a custom Go template.
  • Private key: opt-in — the default excludes it; set IncludePrivateKey only for receivers that actually need it.
  • Retry: configurable count with exponential backoff.
  • Rollback: not supported. The external system owns its own revert story.

02Prerequisites

  • An HTTP endpoint reachable from the backend.
  • Authentication expectation on the receiving side that you can satisfy with static HTTP headers. CertAutoPilot does not build HMAC signatures or OAuth flows for you — a header allowlist is the extent of the webhook surface.

03Create a webhook target

  1. Settings → Distribution → TargetsNew. Module: Webhook.
  2. Fields:
    • URL — the endpoint (HTTPS recommended).
    • Headers — map of name → value. Typical: Authorization: Bearer ... or X-Signature: sha256=....
    • Timeout (seconds) — default 30.
    • Retry count — default 3. Exponential backoff between attempts.
    • Include private key — off by default. Turn on only when the receiver genuinely needs it.
    • Template — optional Go template for a custom body. When empty, the default JSON payload (below) is sent.
    • Concurrency — for fan-out to many webhooks, parallelism cap.
  3. Save → health check sends a harmless OPTIONS (or GET) probe to the URL, whichever the endpoint supports.

04Default JSON payload

{
  "certificate_id": "...",
  "distribution_id": "...",
  "cert_fingerprint": "<sha256 hex>",
  "issued_at": "2026-04-20T12:34:56Z",
  "domains": ["example.com", "www.example.com"],
  "cert_pem": "-----BEGIN CERTIFICATE-----...",
  "chain_pem": "-----BEGIN CERTIFICATE-----...",
  "fullchain_pem": "-----BEGIN CERTIFICATE-----...",
  "private_key_pem": "-----BEGIN PRIVATE KEY-----..."   // only if include_private_key
}

05Custom body templates

The template is a Go text/template with the above fields available. Example — a GitLab CI variable update body:

{
  "variable_type": "env_var",
  "key": "TLS_FULLCHAIN",
  "value": {{ .FullChainPEM | printf "%q" }},
  "protected": true
}

printf "%q" ensures the PEM — which contains newlines — becomes a JSON-safe quoted string.

06Retry semantics

  • Retries fire on 5xx status, transport errors, and read timeouts.
  • 4xx (excluding 408 and 429) are terminal — they indicate a caller bug, not a transient failure.
  • Between attempts the backend sleeps 2^attempt seconds (capped at 60 s).
  • Beyond the configured retry_count, the target is marked failed with error classification io_transient (eligible for per-target retry at the distribution level).

07Security notes

  • Always use HTTPS. The backend allows http:// for localhost-only targets; production endpoints must be HTTPS.
  • Outbound network policy applies: cloud-metadata and link-local addresses are blocked, preventing a misconfigured URL from hitting 169.254.169.254.
  • If include_private_key is ON, review the receiver's handling carefully. The private key is encrypted at rest in CertAutoPilot; after POST, confidentiality depends on the receiver.
  • Signing (HMAC) is your responsibility — you set the X-Signature header yourself by wrapping the webhook target with an intermediary. Native HMAC signing is on the roadmap.

08Rollback — not supported

The external system owns rollback. If you need reversibility, chain the webhook after a rollbackable module (SSH, Vault), so the reversible part covers the critical path.

09Troubleshooting

Webhook times out

Increase the timeout or check whether the receiver is slow-processing the payload synchronously. Async receivers should return 202 Accepted immediately.

"400 Bad Request" after a template change

Template syntax error. Test the template render locally with a known cert's data; CertAutoPilot logs the rendered body when the receiver returns 4xx.

"network policy blocked"

The URL resolves to a metadata IP or a link-local address. Switch to a public DNS name or an explicit allowlist entry.