Skip to main content

Using Secrets in Routes

Learn how to inject encrypted secrets into your route requests using KnoxCall’s template system.

Template Syntax

Secrets are injected using template syntax in your route configuration.

Basic Format

{{secret:SECRET_NAME}}
Components:
  • {{ - Opening delimiter
  • secret: - Type identifier (required)
  • SECRET_NAME - Your secret’s name
  • }} - Closing delimiter

Example

If you have a secret named stripe_prod_key:
{
  "Authorization": "Bearer {{secret:stripe_prod_key}}"
}
At runtime, KnoxCall replaces this with:
{
  "Authorization": "Bearer sk_live_abc123xyz789..."
}

Injecting Secrets in Headers

The most common use case - adding secrets to HTTP headers.

Step 1: Edit Route

  1. Go to Routes
  2. Click on your route
  3. Scroll to Inject Headers section

Step 2: Add Header with Secret

Click Add Header or edit the JSON directly:
{
  "Authorization": "Bearer {{secret:stripe_prod_key}}"
}

Step 3: Save

Click Save Changes ✅ All requests through this route now have the secret injected!

Common Header Patterns

Bearer Token

{
  "Authorization": "Bearer {{secret:api_key}}"
}
Used by: Stripe, GitHub, most REST APIs

API Key Header

{
  "X-API-Key": "{{secret:sendgrid_api_key}}"
}
Used by: SendGrid, Mailgun, custom APIs

Basic Authentication

{
  "Authorization": "Basic {{secret:basic_auth_credentials}}"
}
Note: Secret value should be base64-encoded username:password

Custom Headers

{
  "X-Custom-Token": "{{secret:custom_token}}",
  "X-Webhook-Secret": "{{secret:webhook_secret}}",
  "X-Client-ID": "{{secret:client_id}}"
}

Injecting Secrets in the Request Body

KnoxCall can substitute placeholders inside the request body your client sends — not just headers. This lets your application reference secrets by name (or by ID) without ever holding the actual values. KnoxCall resolves the placeholders server-side, swaps them for the real decrypted values, and forwards the rewritten body upstream.

How it works

  1. Your client sends a POST/PUT/PATCH with placeholder tokens in the body
  2. KnoxCall reads the body, scans for {{secret:name}} / {{secret_id:uuid}} / {{var:key}}
  3. KnoxCall resolves each reference against your tenant’s secrets for the route’s active environment
  4. KnoxCall rewrites Content-Length and forwards the substituted body to the upstream API
  5. Your application never needs to possess the raw credential

Placeholder forms accepted in the body

FormLookupExampleRecommended
{{secret_id:UUID}}By secret UUID — stable, rename-safe{{secret_id:550e8400-e29b-41d4-a716-446655440000}}Preferred
{{secret:NAME}}By secret name — legacy, human-friendly{{secret:stripe_prod_key}}⚠️ Works, but breaks on rename
{{var:QUERY_PARAM}}Value of an incoming query string parameter{{var:user_id}}
Prefer the UUID form ({{secret_id:...}}) wherever possible. A secret’s UUID is permanent and never changes, so your code and templates keep working even if someone renames the secret in the dashboard. The name-based form ({{secret:...}}) is supported for backwards compatibility and readability, but any rename will silently break every reference to it and the next request will hard-fail with “Missing secret”.Also note: {{secret:UUID}} is not a shortcut for ID-based lookup — it would try to find a secret literally named that UUID string and fail. For ID lookups always use the secret_id: prefix.
You can find a secret’s UUID on its detail page in the KnoxCall dashboard, or in the response of the create_secret API / onboarding-agent tool call.

Example — JSON body

Your client code sends this to KnoxCall (UUID form recommended):
POST https://YOUR-SUBDOMAIN.knoxcall.com/v1/charges
Content-Type: application/json
x-knoxcall-route: ROUTE_ID
x-knoxcall-key: YOUR_CLIENT_KEY

{
  "api_key": "{{secret_id:550e8400-e29b-41d4-a716-446655440000}}",
  "amount": 1000,
  "currency": "usd"
}
What the upstream API actually receives:
POST /v1/charges HTTP/1.1
Host: api.stripe.com

{
  "api_key": "sk_live_abc123xyz789...",
  "amount": 1000,
  "currency": "usd"
}

Example — form-urlencoded

POST https://YOUR-SUBDOMAIN.knoxcall.com/send
Content-Type: application/x-www-form-urlencoded

api_key={{secret_id:550e8400-e29b-41d4-a716-446655440000}}&to=%2B15551234567&body=hello

Example — plain text / XML

POST https://YOUR-SUBDOMAIN.knoxcall.com/soap
Content-Type: application/xml

<?xml version="1.0"?>
<Envelope>
  <Auth>{{secret_id:550e8400-e29b-41d4-a716-446655440000}}</Auth>
  <Action>GetInvoice</Action>
</Envelope>

Supported Content-Types

KnoxCall only rewrites bodies for text-shaped Content-Types, so binary uploads stay byte-identical:
Content-TypeBody injection
application/json
application/xml, text/xml, application/*+xml
application/*+json (e.g. application/vnd.api+json)
application/x-www-form-urlencoded
application/javascript, application/ecmascript
text/* (plain, html, csv, etc.)
multipart/form-data❌ passed through unchanged
application/octet-stream, image/*, audio/*, video/*, application/pdf❌ passed through unchanged

Safety rules

  • Tenant isolation — clients can only reference secrets belonging to their own tenant. A UUID guessed from another tenant will hard-fail.
  • Environment-aware — KnoxCall resolves secrets against the route’s active environment. If the secret has no value for that environment, the request fails with a clear error (no silent fallback).
  • Size limit — bodies larger than 1 MiB pass through without substitution to protect the proxy (configurable via BODY_INJECTION_MAX_BYTES).
  • UTF-8 required — the body must be valid UTF-8 with no NUL bytes; anything else is treated as binary and passed through untouched.
  • Hard-fail on missing secrets — referencing a secret that doesn’t exist (or has no value in the active environment) returns an error instead of sending an empty string upstream.
  • Logs are scrubbed — audit logs store the body with placeholders intact, never the resolved secret values.

Do I still need headers?

Header injection and body placeholders are independent and can be combined in the same request. Use headers for stable auth (e.g. Authorization: Bearer) and body placeholders for APIs that expect credentials in the payload or for per-call secret selection.

Common Body Patterns

All examples use the recommended UUID form — substitute your actual secret IDs.

Simple API key in body (UUID, rename-safe)

{ "api_key": "{{secret_id:550e8400-e29b-41d4-a716-446655440000}}" }

Nested objects

{
  "credentials": {
    "username": "admin",
    "password": "{{secret_id:7a4c1f22-9e8b-4d60-9b32-12e1f4b9c8a1}}"
  }
}

Legacy name form (works, but breaks if the secret is renamed)

{ "api_key": "{{secret:service_api_key}}" }

Multiple secrets in one request

{
  "payment_key": "{{secret_id:550e8400-e29b-41d4-a716-446655440000}}",
  "email_key":   "{{secret_id:6b1f2f33-c1a4-4f75-a0b7-8d91e2a6f3c2}}",
  "sms_key":     "{{secret_id:a9c82e44-ff3a-4210-9c63-1b8f5cf9e7d1}}"
}

Mixing a secret with a query-string variable

POST /notify?user_id=42
Content-Type: application/json

{
  "user":  "{{var:user_id}}",
  "token": "{{secret_id:550e8400-e29b-41d4-a716-446655440000}}"
}

Multiple Secrets in One Route

You can use multiple different secrets in the same route configuration.

Example: Multi-Service Integration

Route: notification-service Headers:
{
  "X-Email-Key": "{{secret:sendgrid_api_key}}",
  "X-SMS-Key": "{{secret:twilio_auth_token}}"
}
Body:
{
  "email_config": {
    "api_key": "{{secret:sendgrid_api_key}}"
  },
  "sms_config": {
    "auth_token": "{{secret:twilio_auth_token}}",
    "account_sid": "{{secret:twilio_account_sid}}"
  },
  "push_config": {
    "server_key": "{{secret:fcm_server_key}}"
  }
}
Result: All 4 secrets injected in one request!

Environment-Specific Secrets

Use different secrets per environment (dev, staging, production).

Setup

Create environment-specific secrets:
  1. Production:
    Name: stripe_prod_key
    Value: sk_live_abc123...
    
  2. Staging:
    Name: stripe_staging_key
    Value: sk_test_xyz789...
    
  3. Development:
    Name: stripe_dev_key
    Value: sk_test_dev123...
    

Configure Route Per Environment

Base environment (production):
{
  "Authorization": "Bearer {{secret:stripe_prod_key}}"
}
Override for staging:
{
  "Authorization": "Bearer {{secret:stripe_staging_key}}"
}
Override for development:
{
  "Authorization": "Bearer {{secret:stripe_dev_key}}"
}

How to Set Up

  1. Go to route → Environment tab
  2. Select “staging”
  3. Edit Inject Headers:
    {
      "Authorization": "Bearer {{secret:stripe_staging_key}}"
    }
    
  4. Save
  5. Repeat for “development”
Result:
  • Production uses: stripe_prod_key
  • Staging uses: stripe_staging_key
  • Development uses: stripe_dev_key
All with the same route!

Method-Specific Secrets

Different secrets for different HTTP methods on the same route.

Example Scenario

Route handles both GET (read) and POST (write) to same API:
  • GET needs read-only key
  • POST needs full-access key

Setup

GET method config:
{
  "method": "GET",
  "inject_headers_json": {
    "Authorization": "Bearer {{secret:api_read_only_key}}"
  }
}
POST method config:
{
  "method": "POST",
  "inject_headers_json": {
    "Authorization": "Bearer {{secret:api_full_access_key}}"
  }
}
Result:
  • GET requests use read-only key
  • POST requests use full-access key

Secret Resolution Order

When multiple environments and methods are involved, KnoxCall resolves secrets in this order:
  1. Method-specific environment override
  2. Environment override (if method not specified)
  3. Base route configuration

Example

Request: POST to route in "staging" environment

Lookup order:
1. Check: staging → POST method config → secrets
2. If not found: staging → default config → secrets
3. If not found: base (production) → POST method config → secrets
4. If not found: base (production) → default config → secrets

Template Syntax Rules

✅ Correct Syntax

{
  "Authorization": "Bearer {{secret:stripe_key}}"
}
Rules:
  • Double curly braces: {{ and }}
  • Prefix: secret:
  • No spaces inside braces
  • Lowercase with underscores for secret names

❌ Common Mistakes

// ❌ Single braces
{
  "Authorization": "Bearer {secret:stripe_key}"
}

// ❌ No prefix
{
  "Authorization": "Bearer {{stripe_key}}"
}

// ❌ Wrong prefix
{
  "Authorization": "Bearer {{env:stripe_key}}"
}

// ❌ Spaces inside
{
  "Authorization": "Bearer {{ secret:stripe_key }}"
}

// ❌ Hyphens in name
{
  "Authorization": "Bearer {{secret:stripe-key}}"
}

Real-World Examples

Example 1: Stripe Payments

Secret:
Name: stripe_prod_key
Value: sk_live_abc123xyz789...
Route config:
{
  "inject_headers_json": {
    "Authorization": "Bearer {{secret:stripe_prod_key}}",
    "Content-Type": "application/json"
  }
}
Actual request sent to Stripe:
POST /v1/charges HTTP/1.1
Host: api.stripe.com
Authorization: Bearer sk_live_abc123xyz789...
Content-Type: application/json

{"amount": 1000, "currency": "usd"}

Example 2: SendGrid Email

Secret:
Name: sendgrid_api_key
Value: SG.abc123xyz789...
Route config:
{
  "inject_headers_json": {
    "Authorization": "Bearer {{secret:sendgrid_api_key}}",
    "Content-Type": "application/json"
  },
  "inject_body_json": {
    "personalizations": [{
      "to": [{"email": "user@example.com"}]
    }],
    "from": {"email": "noreply@acme.com"},
    "subject": "Welcome!",
    "content": [{
      "type": "text/html",
      "value": "<p>Welcome!</p>"
    }]
  }
}

Example 3: Database Access

Secret:
Name: postgres_connection_url
Value: postgres://user:pass@host:5432/db
Route config:
{
  "inject_body_json": {
    "connection_string": "{{secret:postgres_connection_url}}",
    "query": "SELECT * FROM users WHERE id = $1",
    "params": [123]
  }
}

Example 4: PrintNode Printing

Secret:
Name: printnode_api_key
Value: abc123xyz789...
Route config:
{
  "inject_headers_json": {
    "Authorization": "Basic {{secret:printnode_api_key}}"
  },
  "inject_body_json": {
    "printerId": 123,
    "title": "Receipt",
    "contentType": "pdf_uri",
    "content": "https://example.com/receipt.pdf",
    "source": "KnoxCall POS"
  }
}

Example 5: Multi-Service Orchestration

Secrets:
stripe_key
sendgrid_key
twilio_key
Route config:
{
  "inject_headers_json": {
    "X-Service": "orchestrator"
  },
  "inject_body_json": {
    "actions": [
      {
        "service": "payment",
        "api_key": "{{secret:stripe_key}}",
        "action": "charge"
      },
      {
        "service": "email",
        "api_key": "{{secret:sendgrid_key}}",
        "action": "send_receipt"
      },
      {
        "service": "sms",
        "api_key": "{{secret:twilio_key}}",
        "action": "send_notification"
      }
    ]
  }
}

Combining Secrets with Other Templates

You can combine secrets with other template variables (coming soon):
{
  "Authorization": "Bearer {{secret:api_key}}",
  "X-User-ID": "{{header:x-user-id}}",
  "X-Request-ID": "{{uuid}}"
}
Currently supported:
  • {{secret:name}} - Inject secrets
Coming soon:
  • {{header:name}} - Pass through headers
  • {{env:name}} - Environment variables
  • {{uuid}} - Generate UUID

Security Considerations

What Gets Logged

In KnoxCall logs:
  • Template syntax shown: {{secret:stripe_key}}
  • Actual value: Never logged
Example log entry:
{
  "request_headers": {
    "Authorization": "Bearer {{secret:stripe_key}}"
  }
}
Plaintext value is not exposed in logs for security.

Secret Transmission

Flow:
1. Client makes request to KnoxCall

2. KnoxCall receives request

3. Loads route configuration

4. Sees {{secret:stripe_key}} template

5. Decrypts secret SERVER-SIDE

6. Replaces template with plaintext

7. Forwards request to backend

8. Backend receives real API key

9. Client NEVER sees the plaintext!

Best Practices

Do:
  • Use secrets for all sensitive values
  • Separate secrets per environment
  • Rotate secrets regularly
  • Use descriptive secret names
  • Document what each secret is for
Don’t:
  • Hardcode API keys in route configs
  • Use production secrets in development
  • Share secrets via email or chat
  • Commit secrets to git
  • Use generic names like “key” or “secret”

Troubleshooting

Secret Not Replacing

Symptom: Backend receives {{secret:stripe_key}} literally. Causes:
  1. Syntax error in template
  2. Secret doesn’t exist
  3. Secret name typo
Debug steps:
  1. Check template syntax: {{secret:name}}
  2. Verify secret exists: Resources → Secrets
  3. Check spelling matches exactly (case-sensitive)
  4. Look at logs for error messages

Wrong Value Injected

Symptom: Wrong API key being used. Causes:
  1. Using wrong environment
  2. Wrong secret name
  3. Secret not configured for environment
Fix:
  1. Check which environment request is using
  2. Verify environment override has correct secret
  3. Check secret name spelling

Secret Not Found Error

Error: Secret 'api_key' not found Solution:
  1. Go to Resources → Secrets
  2. Check secret exists
  3. Verify name matches template exactly
  4. Check no typos: api_key vs api_key_

Value Not Decrypting

Symptom: Encrypted value sent instead of plaintext. Cause: KnoxCall internal error (very rare). Solution:
  1. Check KnoxCall status
  2. Try creating new secret version
  3. Contact support if persists

Testing Secret Injection

Test Endpoint

Use a test endpoint to verify secret injection:
  1. Create test route:
    • Target: https://httpbin.org/anything
    • Method: POST
  2. Add secret in header:
    {
      "X-Test-Secret": "{{secret:test_api_key}}"
    }
    
  3. Make request:
    curl -X POST https://YOUR-SUBDOMAIN.knoxcall.com/anything \
      -H "x-knoxcall-route: test-route" \
      -H "x-knoxcall-key: YOUR_KEY"
    
  4. Check response:
    {
      "headers": {
        "X-Test-Secret": "your-actual-secret-value-here"
      }
    }
    
✅ If you see actual value, injection works!

Quick Reference

Use CaseTemplate SyntaxExample
Header{{secret:name}}"Authorization": "Bearer {{secret:api_key}}"
Body{{secret:name}}"api_key": "{{secret:api_key}}"
MultipleMultiple templates{{secret:key1}} and {{secret:key2}}
EnvironmentDifferent secret per envProduction: {{secret:prod_key}}

Next Steps

Creating Secrets

How to create and manage secrets

Secrets Overview

Complete secrets guide

Environment Basics

Environment-specific secrets

OAuth2 Flow

Auto-refreshing OAuth2 tokens

Pro Tip: Always test secret injection with a test endpoint (like httpbin.org) before using in production. This lets you verify the actual decrypted value is correct.