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
IncludePrivateKeyonly 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
- Settings → Distribution → Targets → New. Module: Webhook.
-
Fields:
- URL — the endpoint (HTTPS recommended).
- Headers — map of name → value. Typical:
Authorization: Bearer ...orX-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.
- Save → health check sends a harmless
OPTIONS(orGET) 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^attemptseconds (capped at 60 s). - Beyond the configured
retry_count, the target is marked failed with error classificationio_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_keyis 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-Signatureheader 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.