Using Secrets
Learn how to reference your workspace secrets in Ductape configurations using the $Secret{} syntax.
The $Secret Syntax
Ductape provides a special syntax to reference secrets in your configurations without exposing the actual values. When you use $Secret{key}, Ductape automatically resolves and decrypts the secret at runtime.
Basic Usage
$Secret{SECRET_KEY}
Replace SECRET_KEY with the key of your secret (the exact key you used when creating the secret).
Where to Use Secrets
In App Authentication
Reference secrets when configuring app authentication:
- TypeScript
- Java
- Go
- .NET
await ductape.product.apps.add({
access_tag: 'stripe_app',
envs: [
{
app_env_slug: 'prd',
product_env_slug: 'prd',
variables: [],
auth: [
{
auth_tag: 'api_key_auth',
data: {
api_key: '$Secret{STRIPE_API_KEY}' // Secret reference
}
}
]
}
]
});
ductape.product.apps.add(Map.of(
"access_tag", "stripe_app",
envs: [
Map.of(
"app_env_slug", "prd",
"product_env_slug", "prd",
variables: [],
auth: [
Map.of(
"auth_tag", "api_key_auth",
data: Map.of(
"api_key", "$SecretMap.of(STRIPE_API_KEY)" // Secret reference
)
)
]
)
]
));
client.product.apps.add({
"access_tag": "stripe_app",
envs: [
{
"app_env_slug": "prd",
"product_env_slug": "prd",
variables: [],
auth: [
{
"auth_tag": "api_key_auth",
data: {
"api_key": "$Secret{STRIPE_API_KEY}" // Secret reference
}
}
]
}
]
});
await ductape.product.apps.add({
["access_tag"] = "stripe_app",
envs: [
{
["app_env_slug"] = "prd",
["product_env_slug"] = "prd",
variables: [],
auth: [
{
["auth_tag"] = "api_key_auth",
data: {
["api_key"] = "$Secret{STRIPE_API_KEY}" // Secret reference
}
}
]
}
]
});
In Environment Variables
Use secrets in app environment variables:
- TypeScript
- Java
- Go
- .NET
await ductape.product.apps.add({
access_tag: 'email_service',
envs: [
{
app_env_slug: 'prd',
product_env_slug: 'prd',
variables: [
{ key: 'SENDGRID_KEY', value: '$Secret{SENDGRID_API_KEY}' },
{ key: 'FROM_EMAIL', value: 'noreply@example.com' }
],
auth: []
}
]
});
ductape.product.apps.add(Map.of(
"access_tag", "email_service",
envs: [
Map.of(
"app_env_slug", "prd",
"product_env_slug", "prd",
variables: [
Map.of( "key", "SENDGRID_KEY", "value", "$SecretMap.of(SENDGRID_API_KEY)" ),
Map.of( "key", "FROM_EMAIL", "value", "noreply@example.com" )
],
auth: []
)
]
));
client.product.apps.add({
"access_tag": "email_service",
envs: [
{
"app_env_slug": "prd",
"product_env_slug": "prd",
variables: [
{ "key": "SENDGRID_KEY", "value": "$Secret{SENDGRID_API_KEY}" },
{ "key": "FROM_EMAIL", "value": "noreply@example.com" }
],
auth: []
}
]
});
await ductape.product.apps.add({
["access_tag"] = "email_service",
envs: [
{
["app_env_slug"] = "prd",
["product_env_slug"] = "prd",
variables: [
{ ["key"] = "SENDGRID_KEY", ["value"] = "$Secret{SENDGRID_API_KEY}" },
{ ["key"] = "FROM_EMAIL", ["value"] = "noreply@example.com" }
],
auth: []
}
]
});
In Action Headers
Reference secrets in custom headers:
- TypeScript
- Java
- Go
- .NET
const result = await ductape.api.run({
app: 'custom_api',
event: 'fetch_data',
input: {
headers: {
'X-API-Key': '$Secret{CUSTOM_API_KEY}',
'X-Webhook-Secret': '$Secret{WEBHOOK_SIGNING_SECRET}'
},
body: {
data: 'payload'
}
}
});
Map<String, Object> result = ductape.api().run(Map<String, Object>.of(
"app", "custom_api",
"event", "fetch_data",
input: Map.of(
headers: Map.of(
'X-API-Key': '$SecretMap.of(CUSTOM_API_KEY)',
'X-Webhook-Secret': '$SecretMap.of(WEBHOOK_SIGNING_SECRET)'
),
body: Map.of(
"data", "payload"
)
)
));
import "context"
result := client.Api.Run(ctx, map[string]any{
"app": "custom_api",
"event": "fetch_data",
input: {
headers: {
'X-API-Key': '$Secret{CUSTOM_API_KEY}',
'X-Webhook-Secret': '$Secret{WEBHOOK_SIGNING_SECRET}'
},
body: {
"data": "payload"
}
}
});
var result = await await ductape.Api.RunAsync(new Dictionary<string, object?>
{
["app"] = "custom_api",
["event"] = "fetch_data",
input: {
headers: {
'X-API-Key': '$Secret{CUSTOM_API_KEY}',
'X-Webhook-Secret': '$Secret{WEBHOOK_SIGNING_SECRET}'
},
body: {
["data"] = "payload"
}
}
});
In Webhook Configurations
Secure your webhooks with secret references:
- TypeScript
- Java
- Go
- .NET
await ductape.product.webhooks.create({
name: 'GitHub Webhook',
tag: 'github_webhook',
signing_secret: '$Secret{GITHUB_WEBHOOK_SECRET}',
// ... other configuration
});
ductape.product.webhooks.create(Map.of(
"name", "GitHub Webhook",
"tag", "github_webhook",
"signing_secret", "$SecretMap.of(GITHUB_WEBHOOK_SECRET)",
// ... other configuration
));
client.product.webhooks.create({
"name": "GitHub Webhook",
"tag": "github_webhook",
"signing_secret": "$Secret{GITHUB_WEBHOOK_SECRET}",
// ... other configuration
});
await ductape.product.webhooks.create({
["name"] = "GitHub Webhook",
["tag"] = "github_webhook",
["signing_secret"] = "$Secret{GITHUB_WEBHOOK_SECRET}",
// ... other configuration
});
Copying Secret References
In the Ductape Workbench, you can quickly copy the token reference syntax:
- Navigate to Workspace Settings > Secrets
- Find the secret you want to reference
- Click the copy icon next to the secret name
- The
$Secret{SECRET_KEY}syntax is copied to your clipboard - Paste it wherever you need to use the secret
Runtime Resolution
When Ductape processes a configuration containing $Secret{} references:
- Detection: The system identifies all
$Secret{}patterns - Validation: Checks if the referenced secrets exist and are accessible
- Scope Check: Verifies the secret's scope includes the current app
- Environment Check: Confirms the secret is available in the current environment
- Decryption: Retrieves and decrypts the secret value
- Substitution: Replaces the
$Secret{}reference with the actual value
Scope and Environment Restrictions
Secrets respect their scope and environment restrictions at runtime:
Scope Example
- TypeScript
- Java
- Go
- .NET
// Secret created with limited scope
await ductape.secrets.create({
key: 'STRIPE_KEY',
value: 'Bearer sk_live_xxx',
scope: ['payment_app'] // Only accessible by payment_app
});
// This works - payment_app is in scope
await ductape.api.run({
app: 'payment_app',
// ...
input: {
headers: { 'Authorization': '$Secret{STRIPE_KEY}' }
}
});
// This fails - analytics_app is not in scope
await ductape.api.run({
app: 'analytics_app',
// ...
input: {
headers: { 'Authorization': '$Secret{STRIPE_KEY}' } // Error!
}
});
// Secret created with limited scope
ductape.secretService().create(Map<String, Object>.of(
"key", "STRIPE_KEY",
"value", "Bearer sk_live_xxx",
scope: ['payment_app'] // Only accessible by payment_app
));
// This works - payment_app is in scope
ductape.api().run(Map<String, Object>.of(
"app", "payment_app",
// ...
input: Map.of(
headers: Map.of( 'Authorization': '$SecretMap.of(STRIPE_KEY)' )
)
));
// This fails - analytics_app is not in scope
ductape.api().run(Map<String, Object>.of(
"app", "analytics_app",
// ...
input: Map.of(
headers: Map.of( 'Authorization': '$SecretMap.of(STRIPE_KEY)' ) // Error!
)
));
import "context"
// Secret created with limited scope
client.secrets.create({
"key": "STRIPE_KEY",
"value": "Bearer sk_live_xxx",
scope: ['payment_app'] // Only accessible by payment_app
});
// This works - payment_app is in scope
client.Api.Run(ctx, map[string]any{
"app": "payment_app",
// ...
input: {
headers: { 'Authorization': '$Secret{STRIPE_KEY}' }
}
});
// This fails - analytics_app is not in scope
client.Api.Run(ctx, map[string]any{
"app": "analytics_app",
// ...
input: {
headers: { 'Authorization': '$Secret{STRIPE_KEY}' } // Error!
}
});
// Secret created with limited scope
await ductape.secrets.create({
["key"] = "STRIPE_KEY",
["value"] = "Bearer sk_live_xxx",
scope: ['payment_app'] // Only accessible by payment_app
});
// This works - payment_app is in scope
await await ductape.Api.RunAsync(new Dictionary<string, object?>
{
["app"] = "payment_app",
// ...
input: {
headers: { 'Authorization': '$Secret{STRIPE_KEY}' }
}
});
// This fails - analytics_app is not in scope
await await ductape.Api.RunAsync(new Dictionary<string, object?>
{
["app"] = "analytics_app",
// ...
input: {
headers: { 'Authorization': '$Secret{STRIPE_KEY}' } // Error!
}
});
Environment Example
- TypeScript
- Java
- Go
- .NET
// Secret created for production only
await ductape.secrets.create({
key: 'PROD_DATABASE_URL',
value: 'postgres://prod-server/db',
envs: ['prd']
});
// This works - running in production
await ductape.api.run({
// ...
});
// This fails - secret not available in dev
await ductape.api.run({
// ...
input: {
body: { db_url: '$Secret{PROD_DATABASE_URL}' } // Error!
}
});
// Secret created for production only
ductape.secretService().create(Map<String, Object>.of(
"key", "PROD_DATABASE_URL",
"value", "postgres://prod-server/db",
envs: ['prd']
));
// This works - running in production
ductape.api().run(Map<String, Object>.of(
// ...
));
// This fails - secret not available in dev
ductape.api().run(Map<String, Object>.of(
// ...
input: Map.of(
body: Map.of( "db_url", "$SecretMap.of(PROD_DATABASE_URL)" ) // Error!
)
));
import "context"
// Secret created for production only
client.secrets.create({
"key": "PROD_DATABASE_URL",
"value": "postgres://prod-server/db",
envs: ['prd']
});
// This works - running in production
client.Api.Run(ctx, map[string]any{
// ...
});
// This fails - secret not available in dev
client.Api.Run(ctx, map[string]any{
// ...
input: {
body: { "db_url": "$Secret{PROD_DATABASE_URL}" } // Error!
}
});
// Secret created for production only
await ductape.secrets.create({
["key"] = "PROD_DATABASE_URL",
["value"] = "postgres://prod-server/db",
envs: ['prd']
});
// This works - running in production
await await ductape.Api.RunAsync(new Dictionary<string, object?>
{
// ...
});
// This fails - secret not available in dev
await await ductape.Api.RunAsync(new Dictionary<string, object?>
{
// ...
input: {
body: { ["db_url"] = "$Secret{PROD_DATABASE_URL}" } // Error!
}
});
Multiple Secrets in One Configuration
You can use multiple secret references in the same configuration:
- TypeScript
- Java
- Go
- .NET
await ductape.product.apps.add({
access_tag: 'multi_service_app',
envs: [
{
app_env_slug: 'prd',
product_env_slug: 'prd',
variables: [
{ key: 'DB_HOST', value: '$Secret{DATABASE_HOST}' },
{ key: 'DB_USER', value: '$Secret{DATABASE_USER}' },
{ key: 'DB_PASS', value: '$Secret{DATABASE_PASSWORD}' },
{ key: 'REDIS_URL', value: '$Secret{REDIS_CONNECTION_STRING}' },
{ key: 'S3_KEY', value: '$Secret{AWS_ACCESS_KEY}' },
{ key: 'S3_SECRET', value: '$Secret{AWS_SECRET_KEY}' }
],
auth: [
{
auth_tag: 'oauth',
data: {
client_id: '$Secret{OAUTH_CLIENT_ID}',
client_secret: '$Secret{OAUTH_CLIENT_SECRET}'
}
}
]
}
]
});
ductape.product.apps.add(Map.of(
"access_tag", "multi_service_app",
envs: [
Map.of(
"app_env_slug", "prd",
"product_env_slug", "prd",
variables: [
Map.of( "key", "DB_HOST", "value", "$SecretMap.of(DATABASE_HOST)" ),
Map.of( "key", "DB_USER", "value", "$SecretMap.of(DATABASE_USER)" ),
Map.of( "key", "DB_PASS", "value", "$SecretMap.of(DATABASE_PASSWORD)" ),
Map.of( "key", "REDIS_URL", "value", "$SecretMap.of(REDIS_CONNECTION_STRING)" ),
Map.of( "key", "S3_KEY", "value", "$SecretMap.of(AWS_ACCESS_KEY)" ),
Map.of( "key", "S3_SECRET", "value", "$SecretMap.of(AWS_SECRET_KEY)" )
],
auth: [
Map.of(
"auth_tag", "oauth",
data: Map.of(
"client_id", "$SecretMap.of(OAUTH_CLIENT_ID)",
"client_secret", "$SecretMap.of(OAUTH_CLIENT_SECRET)"
)
)
]
)
]
));
client.product.apps.add({
"access_tag": "multi_service_app",
envs: [
{
"app_env_slug": "prd",
"product_env_slug": "prd",
variables: [
{ "key": "DB_HOST", "value": "$Secret{DATABASE_HOST}" },
{ "key": "DB_USER", "value": "$Secret{DATABASE_USER}" },
{ "key": "DB_PASS", "value": "$Secret{DATABASE_PASSWORD}" },
{ "key": "REDIS_URL", "value": "$Secret{REDIS_CONNECTION_STRING}" },
{ "key": "S3_KEY", "value": "$Secret{AWS_ACCESS_KEY}" },
{ "key": "S3_SECRET", "value": "$Secret{AWS_SECRET_KEY}" }
],
auth: [
{
"auth_tag": "oauth",
data: {
"client_id": "$Secret{OAUTH_CLIENT_ID}",
"client_secret": "$Secret{OAUTH_CLIENT_SECRET}"
}
}
]
}
]
});
await ductape.product.apps.add({
["access_tag"] = "multi_service_app",
envs: [
{
["app_env_slug"] = "prd",
["product_env_slug"] = "prd",
variables: [
{ ["key"] = "DB_HOST", ["value"] = "$Secret{DATABASE_HOST}" },
{ ["key"] = "DB_USER", ["value"] = "$Secret{DATABASE_USER}" },
{ ["key"] = "DB_PASS", ["value"] = "$Secret{DATABASE_PASSWORD}" },
{ ["key"] = "REDIS_URL", ["value"] = "$Secret{REDIS_CONNECTION_STRING}" },
{ ["key"] = "S3_KEY", ["value"] = "$Secret{AWS_ACCESS_KEY}" },
{ ["key"] = "S3_SECRET", ["value"] = "$Secret{AWS_SECRET_KEY}" }
],
auth: [
{
["auth_tag"] = "oauth",
data: {
["client_id"] = "$Secret{OAUTH_CLIENT_ID}",
["client_secret"] = "$Secret{OAUTH_CLIENT_SECRET}"
}
}
]
}
]
});
Troubleshooting
Secret Not Found
Error: Secret 'MY_SECRET' not found
Solution: Verify the secret exists in your workspace and the key is spelled correctly (case-sensitive).
Scope Mismatch
Error: Secret 'STRIPE_KEY' is not accessible by app 'other_app'
Solution: Update the secret's scope to include the app, or use a different secret.
Environment Mismatch
Error: Secret 'PROD_KEY' is not available in environment 'dev'
Solution: Update the secret's envs array to include the environment, or use an environment-specific secret.
Expired Secret
Error: Secret 'OLD_KEY' has expired
Solution: Update the secret with a new value and expiration date, or create a new secret.
Security Considerations
- Never log secret values: Avoid logging configurations that contain resolved secrets
- Use narrow scopes: Only grant access to apps that genuinely need the secret
- Set expirations: Use
expires_atto enforce regular rotation - Audit usage: Review which apps and environments use each secret
- Rotate compromised secrets: If a secret is exposed, immediately update or delete it
Next Steps
- Managing Secrets - Learn CRUD operations for secrets
- Overview - Return to the secrets overview