This guide explains how to create custom hooks that integrate ldactl with your preferred secrets management systems, configuration files, or other services.
ldactl hooks are executable scripts that receive LaunchDarkly environment configuration changes and can perform actions like:
- Syncing credentials to secrets managers (AWS Secrets Manager, HashiCorp Vault, etc.)
- Updating configuration files
- Restarting services
- Notifying external systems
ldactl supports two execution modes for hooks:
When using --exec-mode env (or omitting the flag), ldactl sets environment variables that your hook can read:
ldactl --exec ./my-hook.shWhen using --exec-mode change-json, ldactl writes the change event as JSON to STDIN:
ldactl --exec-mode change-json --exec ./my-hook.shWhen using environment variable mode, ldactl sets the following variables:
LDAC_EVENT_KIND- The type of event:insert,update,delete, orinitialized
LDAC_PROJECT_KEY- The LaunchDarkly project keyLDAC_ENV_KEY- The environment keyLDAC_ENV_ID- The environment ID (client-side key)LDAC_MOBILE_KEY- The mobile SDK keyLDAC_SDK_KEY- The server-side SDK key
Here's a minimal hook template:
#!/usr/bin/env bash
set -euo pipefail
# Get event kind from environment variable
event_kind="${LDAC_EVENT_KIND:-}"
# Exit early if no event kind
if [[ -z "$event_kind" ]]; then
echo "Error: LDAC_EVENT_KIND environment variable not set" >&2
exit 1
fi
# Skip initialization events
if [[ "$event_kind" == "initialized" ]]; then
exit 0
fi
# Extract environment data
project="${LDAC_PROJECT_KEY:-}"
env_key="${LDAC_ENV_KEY:-}"
client_id="${LDAC_ENV_ID:-}"
mobile_key="${LDAC_MOBILE_KEY:-}"
server_key="${LDAC_SDK_KEY:-}"
# Validate required values
if [[ -z "$project" || -z "$env_key" ]]; then
echo "Error: Required environment variables not set" >&2
exit 1
fi
# Handle different event types
case "$event_kind" in
insert|update)
echo "Creating/updating credentials for $project/$env_key"
# Your logic here
;;
delete)
echo "Deleting credentials for $project/$env_key"
# Your logic here
;;
*)
echo "Error: Unknown event kind: $event_kind" >&2
exit 1
;;
esacFor JSON mode hooks, read the change event from STDIN:
#!/usr/bin/env bash
set -euo pipefail
# Read JSON from STDIN
json_input=$(cat)
# Extract event kind using jq
event_kind=$(echo "$json_input" | jq -r '.kind')
# Skip initialization events
if [[ "$event_kind" == "initialized" ]]; then
exit 0
fi
# Extract environment data
project=$(echo "$json_input" | jq -r '.projectKey')
env_key=$(echo "$json_input" | jq -r '.environmentKey')
client_id=$(echo "$json_input" | jq -r '.environmentId')
mobile_key=$(echo "$json_input" | jq -r '.mobileKey')
server_key=$(echo "$json_input" | jq -r '.sdkKey')
# Handle different event types
case "$event_kind" in
insert|update)
echo "Creating/updating credentials for $project/$env_key"
# Your logic here
;;
delete)
echo "Deleting credentials for $project/$env_key"
# Your logic here
;;
esac- Use
set -euo pipefailfor strict error handling - Validate required environment variables
- Handle missing or null values gracefully
- Exit with non-zero status on errors
- Always check for the
initializedevent and exit early - Handle all event types (
insert,update,delete) - Provide meaningful error messages for unknown event types
- Use environment variables for configuration with sensible defaults
- Document all required and optional configuration variables
- Validate configuration before processing
- Design hooks to be idempotent (safe to run multiple times)
- Check if resources exist before creating them
- Handle updates gracefully
- Log important operations for debugging
- Use stderr for error messages
- Include context (project, environment) in log messages
Here's a complete example for syncing to HashiCorp Vault:
#!/usr/bin/env bash
set -euo pipefail
# Configuration
VAULT_ADDR="${VAULT_ADDR:-http://localhost:8200}"
VAULT_TOKEN="${VAULT_TOKEN:-}"
VAULT_PATH_PREFIX="${VAULT_PATH_PREFIX:-launchdarkly}"
# Get event kind
event_kind="${LDAC_EVENT_KIND:-}"
if [[ -z "$event_kind" ]]; then
echo "Error: LDAC_EVENT_KIND environment variable not set" >&2
exit 1
fi
if [[ "$event_kind" == "initialized" ]]; then
exit 0
fi
# Extract environment data
project="${LDAC_PROJECT_KEY:-}"
env_key="${LDAC_ENV_KEY:-}"
client_id="${LDAC_ENV_ID:-}"
mobile_key="${LDAC_MOBILE_KEY:-}"
server_key="${LDAC_SDK_KEY:-}"
# Validate required values
if [[ -z "$project" || -z "$env_key" ]]; then
echo "Error: Required environment variables not set" >&2
exit 1
fi
if [[ -z "$VAULT_TOKEN" ]]; then
echo "Error: VAULT_TOKEN environment variable not set" >&2
exit 1
fi
# Build vault path
vault_path="${VAULT_PATH_PREFIX}/${project}/${env_key}"
create_or_update_secret() {
local key="$1"
local value="$2"
if [[ -z "$value" || "$value" == "null" ]]; then
return 0
fi
echo "Writing $key to vault path: $vault_path"
vault kv put "$vault_path" "$key"="$value"
}
delete_secret() {
echo "Deleting vault path: $vault_path"
vault kv delete "$vault_path" || true
}
case "$event_kind" in
insert|update)
create_or_update_secret "server_key" "$server_key"
create_or_update_secret "mobile_key" "$mobile_key"
create_or_update_secret "client_id" "$client_id"
;;
delete)
delete_secret
;;
*)
echo "Error: Unknown event kind: $event_kind" >&2
exit 1
;;
esacCreate a test script that sets the required environment variables:
#!/usr/bin/env bash
export LDAC_EVENT_KIND="insert"
export LDAC_PROJECT_KEY="test-project"
export LDAC_ENV_KEY="test-env"
export LDAC_ENV_ID="test-client-id"
export LDAC_MOBILE_KEY="test-mobile-key"
export LDAC_SDK_KEY="test-server-key"
./my-hook.shTest all event types:
insert- New environment createdupdate- Environment updateddelete- Environment deletedinitialized- Initial sync (should exit early)
- Missing environment variables
- Invalid event types
- Network/service failures
FROM alpine:latest
RUN apk add --no-cache bash curl jq
COPY hooks/my-hook.sh /hooks/
RUN chmod +x /hooks/my-hook.sh
ENTRYPOINT ["ldactl", "--exec", "/hooks/my-hook.sh"]apiVersion: batch/v1
kind: CronJob
metadata:
name: ldactl-sync
spec:
schedule: "0 */6 * * *" # Every 6 hours
jobTemplate:
spec:
template:
spec:
containers:
- name: ldactl
image: my-ldactl-hook:latest
env:
- name: LD_RELAY_AUTO_CONFIG_KEY
valueFrom:
secretKeyRef:
name: ldactl-secrets
key: relay-key- Permission Denied: Ensure your hook script is executable (
chmod +x) - Service Connection Failures: Implement retry logic and proper error handling for external service calls
- Add verbose logging to your hook
- Test with
--onceflag for one-time execution - Use
echostatements to verify environment variables - Check ldactl logs for any errors in hook execution
Check the hooks/ directory for complete examples:
write-aws-ssm.sh- AWS Secrets Manager integrationwrite-gcp-secrets.sh- Google Cloud Secret Managerwrite-k8s-secrets.sh- Kubernetes secretswrite-vault-kv.sh- HashiCorp Vault KVwrite-plist.sh- macOS plist files