CRD Reference
The Hookbase Kubernetes Operator provides 10 Custom Resource Definitions. All resources use the API group hookbase.io/v1alpha1.
Core Resources
WebhookSource
Defines a webhook ingestion endpoint.
apiVersion: hookbase.io/v1alpha1
kind: WebhookSource
metadata:
name: github-webhooks
spec:
name: GitHub Webhooks # Display name
slug: github-webhooks # URL slug (immutable after creation)
provider: github # Provider id for signature verification
description: "GitHub org webhooks"
signingSecretRef: # K8s Secret holding the signing secret (sent to the API)
name: github-webhook-secret
key: secret
rejectInvalidSignatures: true # Reject webhooks with invalid signatures (default: false)
rateLimitPerMinute: 1000 # Rate limit for incoming webhooks (paid plans)
ipFilterMode: allowlist # "none", "allowlist", "denylist", "both"
ipAllowlist: # CIDR ranges to allow
- "192.168.0.0/16"
dedupStrategy: auto # "auto", "provider_id", "payload_hash", "idempotency_key", "none"
dedupWindowHours: 24 # Deduplication window (1-168)
transientMode: false # Never store payloads at rest
allowedMethods: ["POST"] # Accepted HTTP verbs (omit for any)
apiKeyRef: # Override API key (optional)
name: my-secret
key: apiKey
status:
sourceId: "11111111-1111-4111-8111-111111111111" # API resource ID
ingestUrl: "https://..." # Webhook ingestion URL
verifiesWithoutSecret: false # true for built-in-key providers (wise, aws-sns)
awaitingSigningSecret: false # true while a vendor-issued secret (stripe, asana, …) is not set
conditions: # Standard K8s conditions
- type: Ready
status: "True"
reason: Synced
- type: Synced
status: "True"
observedGeneration: 1
lastSyncTime: "2026-02-17T12:00:00Z"Info
These field names apply from operator v0.4.0. Earlier releases used verifySignature (replaced
by rejectInvalidSignatures), accepted only none/allowlist/denylist for ipFilterMode and a
different dedupStrategy enum, and did not send signingSecretRef to the API.
Providers: provider takes any provider id Hookbase verifies — see the
integrations page or the
Provider Catalog API (use an entry's hookbaseProvider).
Omitting it falls back to generic.
Signing secret: whether signingSecretRef is needed depends on the provider
(Signing secrets by provider):
- Vendor-issued secret or public key (Stripe, Standard Webhooks
whsec_keys, Discord, …): create the Secret with the vendor's value and reference it. The operator sends it to the API and never overwrites it. - Hookbase-generated secret: omit
signingSecretRefand the operator stores the generated secret in a Secret named<metadata.name>-signing-secret(keysigningSecret) for you to paste into the vendor. - Built-in key (
wise,aws-sns): no secret at all;status.verifiesWithoutSecretistrue. asana/notion: the vendor issues the secret after registration;status.awaitingSigningSecretstaystrueuntil it is set.
For provider: whatsapp, set verifyTokenRef (name, key, default key verifyToken) to choose
the Meta Verify token yourself. Without it, the generated token is written to the same generated
Secret (key verifyToken) so you can type it into Meta's "Verify and save". The operator never
writes to a Secret you reference; a referenced Secret's new value is sent on the next sync (within
the requeue interval, 5 minutes by default).
WebhookDestination
Defines where webhooks are delivered.
apiVersion: hookbase.io/v1alpha1
kind: WebhookDestination
metadata:
name: internal-api
spec:
name: Internal API
slug: internal-api # URL slug (immutable)
url: "https://api.example.com/webhooks"
method: POST # HTTP method (default: POST)
headers: # Custom headers
X-Custom-Header: "value"
authType: bearer # "bearer", "basic", "header", "none"
authSecretRef: # K8s Secret for auth credentials
name: api-auth-secret
key: token
timeoutMs: 10000 # Request timeout (milliseconds)
rateLimitPerMinute: 500 # Outbound rate limit
useStaticIp: true # Route via static IP proxy (paid plans)
apiKeyRef: # Override API key (optional)
name: my-secret
key: apiKey
status:
destinationId: "22222222-2222-4222-8222-222222222222"
conditions:
- type: Ready
status: "True"
observedGeneration: 1
lastSyncTime: "2026-02-17T12:00:00Z"Warehouse Destination (S3)
For warehouse destinations (S3, R2, GCS, Azure Blob), use type and configSecretRef instead of url:
apiVersion: hookbase.io/v1alpha1
kind: WebhookDestination
metadata:
name: s3-warehouse
spec:
name: S3 Data Warehouse
type: s3 # "s3", "r2", "gcs", "azure_blob"
configSecretRef: # K8s Secret with warehouse credentials
name: s3-warehouse-creds
key: config
fieldMapping: # Map webhook fields to warehouse columns
- source: "$.id"
target: event_id
type: string
- source: "$.body"
target: payload
type: json
- source: "$.headers.x-event-type"
target: event_type
type: string
default: "unknown"
- source: "$.timestamp"
target: received_at
type: timestamp
apiKeyRef:
name: my-secret
key: apiKey
---
# Secret with warehouse credentials
apiVersion: v1
kind: Secret
metadata:
name: s3-warehouse-creds
type: Opaque
stringData:
bucket: my-webhook-data
region: us-east-1
accessKeyId: AKIA...
secretAccessKey: "secret..."
prefix: webhooks/Supported warehouse types: s3, r2 (Cloudflare R2), gcs (Google Cloud Storage), azure_blob (Azure Blob Storage)
WebhookRoute
Connects a source to a destination with optional filtering and transformation.
apiVersion: hookbase.io/v1alpha1
kind: WebhookRoute
metadata:
name: github-to-api
spec:
name: GitHub to Internal API
sourceRef: github-webhooks # WebhookSource name in same namespace
destinationRef: internal-api # WebhookDestination name
filterRef: production-only # WebhookFilter name (optional)
# OR inline filter conditions (mutually exclusive with filterRef):
filterConditions:
- field: headers.x-github-event
operator: in
value: "push,pull_request,release"
filterLogic: AND # "AND" or "OR" (default: AND)
transformRef: strip-headers # WebhookTransform name (optional)
schemaRef: github-schema # WebhookSchema name (optional)
priority: 10 # Route priority (lower = higher priority)
isActive: true # Enable/disable the route
failoverDestinationRefs: # Backup destinations
- backup-api
circuitBreaker:
failureThreshold: 5 # Failures before circuit opens
cooldownSeconds: 60 # Cooldown before probing
apiKeyRef: # Override API key (optional)
name: my-secret
key: apiKey
status:
routeId: "rt_def456"
resolvedSourceId: "11111111-1111-4111-8111-111111111111" # Resolved API ID from sourceRef
resolvedDestinationId: "22222222-2222-4222-8222-222222222222"
conditions:
- type: Ready
status: "True"
observedGeneration: 1
lastSyncTime: "2026-02-17T12:00:00Z"WebhookTunnel
Creates a public URL that tunnels to a local Kubernetes service.
apiVersion: hookbase.io/v1alpha1
kind: WebhookTunnel
metadata:
name: dev-tunnel
spec:
name: Dev Tunnel
subdomain: my-app-dev # Custom subdomain (immutable, optional)
targetPort: 8080 # Port to forward to
targetService: my-app # K8s Service name (optional, defaults to localhost)
authTokenSecretRef: # Custom Secret name for auth token (optional)
name: my-tunnel-auth
sidecarInjection: # Automatic sidecar injection (optional)
deploymentRef: my-app # Target Deployment name
agentImage: "ghcr.io/hookbase/hookbase-agent:v0.1.0" # Override agent image
resources:
requests:
cpu: 10m
memory: 16Mi
limits:
cpu: 100m
memory: 64Mi
apiKeyRef:
name: my-secret
key: apiKey
status:
tunnelId: "dddddddd-dddd-4ddd-8ddd-dddddddd0002"
publicUrl: "https://my-app-dev.tunnel.hookbase.app"
connectionStatus: "configured" # "configured", "connected", "disconnected"
sidecarInjected: true
conditions:
- type: Ready
status: "True"
observedGeneration: 1
lastSyncTime: "2026-02-17T12:00:00Z"Pipeline Resources
WebhookTransform
Defines a transformation applied to webhook payloads.
apiVersion: hookbase.io/v1alpha1
kind: WebhookTransform
metadata:
name: strip-headers
spec:
name: Strip Headers
slug: strip-headers
description: "Remove sensitive headers before forwarding"
transformType: jsonata # "jsonata", "javascript", "mapping"
code: |
{
"body": $,
"timestamp": $now()
}
# OR load code from a ConfigMap:
# codeFrom:
# configMapKeyRef:
# name: transforms
# key: strip-headers.jsonata
inputFormat: json
outputFormat: json
apiKeyRef:
name: my-secret
key: apiKey
status:
transformId: "88888888-8888-4888-8888-888888880011"
conditions:
- type: Ready
status: "True"WebhookFilter
Defines conditions that determine which webhooks are routed.
apiVersion: hookbase.io/v1alpha1
kind: WebhookFilter
metadata:
name: production-only
spec:
name: Production Only
slug: production-only
description: "Only route production events"
conditions:
- field: headers.x-environment
operator: equals
value: "production"
- field: body.action
operator: in
value: "created,updated"
logic: AND # "AND" or "OR"
apiKeyRef:
name: my-secret
key: apiKey
status:
filterId: "77777777-7777-4777-8777-777777770013"
conditions:
- type: Ready
status: "True"Supported operators: equals, not_equals, contains, not_contains, starts_with, ends_with, in, not_in, exists, not_exists, regex, gt, gte, lt, lte
WebhookSchema
Defines JSON Schema validation for incoming webhooks.
apiVersion: hookbase.io/v1alpha1
kind: WebhookSchema
metadata:
name: order-schema
spec:
name: Order Schema
slug: order-schema
description: "Validate order webhook payloads"
jsonSchema: |
{
"type": "object",
"required": ["order_id", "amount"],
"properties": {
"order_id": { "type": "string" },
"amount": { "type": "number", "minimum": 0 }
}
}
# OR load from ConfigMap:
# jsonSchemaFrom:
# configMapKeyRef:
# name: schemas
# key: order.json
apiKeyRef:
name: my-secret
key: apiKey
status:
schemaId: "66666666-6666-4666-8666-666666660002"
conditions:
- type: Ready
status: "True"Infrastructure Resources
HookbaseCronJob
Defines a scheduled HTTP request managed by Hookbase.
apiVersion: hookbase.io/v1alpha1
kind: HookbaseCronJob
metadata:
name: hourly-sync
spec:
name: Hourly Sync
description: "Sync data every hour"
cronExpression: "0 * * * *"
url: "https://api.example.com/sync"
method: POST
headers:
Authorization: "Bearer token"
payload: |
{"action": "sync"}
# OR load from ConfigMap:
# payloadFrom:
# configMapKeyRef:
# name: payloads
# key: sync.json
timezone: "UTC"
timeoutMs: 30000
isActive: true
useStaticIp: true # Route via static IP proxy (paid plans; opt-in, default false)
apiKeyRef:
name: my-secret
key: apiKey
status:
cronJobId: "cron_stu234"
lastRunAt: "2026-02-17T11:00:00Z"
nextRunAt: "2026-02-17T12:00:00Z"
conditions:
- type: Ready
status: "True"HookbaseAPIKey
Provisions scoped API keys and stores them as Kubernetes Secrets.
apiVersion: hookbase.io/v1alpha1
kind: HookbaseAPIKey
metadata:
name: team-key
labels:
hookbase.io/default: "true" # Make this the default key for the namespace
spec:
name: Team API Key
scopes:
- read
- write
expiresInDays: 90
secretRef: # Where to store the raw API key
name: hookbase-team-key
key: apiKey
status:
apiKeyId: "55555555-5555-4555-8555-555555550003"
keyPrefix: "whr_abc"
expiresAt: "2026-05-17T00:00:00Z"
conditions:
- type: Ready
status: "True"Available scopes: read, write, delete. These are global verbs, not per-resource permissions; scopes is passed straight through to POST /api/api-keys, so any other value makes the key fail to create with 400 Invalid scopes. Omitting scopes yields ["read", "write"].
Warning
expiresInDays is the CRD's field and days is the correct unit to write here — the API reads expiresIn in seconds, and the Go SDK the operator is built on converts for you. That conversion is not in a published release yet, so an operator built from go v1.8.0 or earlier drops the field silently and creates a key with no expiry. Until you upgrade, treat operator-managed keys as non-expiring and rotate them yourself.
HookbaseConfig
Cluster-scoped configuration for the operator.
apiVersion: hookbase.io/v1alpha1
kind: HookbaseConfig
metadata:
name: hookbase-config # Only one per cluster
spec:
apiBaseUrl: "https://api.hookbase.app"
defaultBootstrapAPIKeySecretRef:
name: hookbase-bootstrap-key
namespace: hookbase-system
key: apiKey
defaultAgentImage: "ghcr.io/hookbase/hookbase-agent:v0.1.0"
namespaceSelector: # Limit which namespaces the operator watches
matchLabels:
hookbase.io/enabled: "true"
defaultReconcileInterval: "5m"Common Patterns
Shared Types
All CRDs that reference K8s Secrets use the SecretKeyRef type:
secretRef:
name: secret-name # K8s Secret name in the same namespace
key: secret-key # Key within the Secret (defaults to "apiKey")Conditions
Every CRD status includes standard Kubernetes conditions:
| Condition | Meaning |
|---|---|
Ready | Overall resource health |
Synced | Last successful sync with Hookbase API |
Finalizers
All resources use the finalizer hookbase.io/finalizer. This ensures that when a CRD is deleted via kubectl delete, the corresponding API resource is also cleaned up.
Labels
| Label | Usage |
|---|---|
hookbase.io/default=true | Marks a HookbaseAPIKey as the default for its namespace |
hookbase.io/tunnel={name} | Applied to auth Secrets created by tunnel controller |
See Also
- Kubernetes Operator Guide -- installation and usage
- Helm Chart Reference -- all Helm values