DocumentationSecrets

Secrets

Store API tokens, signing secrets and passwords as encrypted, write-only secret service connections and import them into Webhook Relay functions with require("secret:alias").

Secret service connections store API tokens, signing secrets and passwords for your Functions. They are encrypted at rest and write-only — once saved, the value can be used by your functions but never read back through the API or the dashboard.

Instead of pasting credentials into function source code, functions import a secret by alias:

const apiToken = require("secret:api-token")

const resp = http.get("https://api.example.com/data", {
  headers: { Authorization: "Bearer " + apiToken }
})
local api_token = require("secret:api-token")
local http = require("http")

local resp, err = http.get("https://api.example.com/data", {
  headers = { Authorization = "Bearer " .. api_token }
})
if err then error(err) end

How it works

  1. Declare — your function imports require("secret:<alias>"). Secret imports are detected statically when the function is created or updated, so the platform always knows which secrets a function needs.
  2. Store — create a Secret service connection at my.webhookrelay.com/service-connections holding the credential value.
  3. Bind — on the function's Connections tab, bind each alias to one of your secret connections. The same connection can be reused across many functions.
  4. Run — before your code executes, every imported alias is preflighted. If an alias is not bound, execution stops with a clear error before any user code runs. Resolved values are injected only into the execution runtime.

Alias names must start with a letter and contain only letters, numbers, underscores or hyphens (64 characters max) — for example api-token, stripe_signing_secret.

Binding secrets in the dashboard

Open a function and switch to the Connections tab. It lists every secret: import found in the source, together with its binding status:

  • missing — the alias is not bound yet. Use the deep-linked setup flow to create a new secret (or pick an existing one) and bind it; you'll be returned to the function afterwards.
  • connected — the alias is bound to a healthy secret connection and the function is ready to run. A bound alias can also show the underlying connection's pending or error status.

When you remove a require("secret:...") import from the source, its stale binding is pruned automatically on save. Restoring an older function version re-checks the imports of that version.

Managing secrets via the API

Secret connections use the standard service connections API with service_type: secret. Function bindings have their own endpoints:

MethodPathDescription
GET/v1/functions/{id}/connectionsList connection requirements (alias, status, bound connection)
PUT/v1/functions/{id}/connections/{alias}Bind an alias to a secret connection ({"service_connection_id": "..."})
DELETE/v1/functions/{id}/connections/{alias}Unbind an alias

Function reads also return a requirements field so automation (including the MCP server) can detect unbound secrets. All endpoints are covered by audit logs.

Secrets vs function config (cfg)

Functions also have a per-function config store read with cfg.get("key"). When should you use which?

require("secret:alias")cfg.get("key")
ScopeAccount-wide, reusable across functionsPer function
Readable after savingNo — write-onlyYes
Missing valueExecution blocked before user code runsReturns empty value at runtime
RedactionExact values redacted from errors and logsNot redacted
Best forAPI tokens, signing secrets, passwordsNon-sensitive tunables, flags, endpoints

For anything you'd call a credential, prefer secret connections.

Security

  • Encryption at rest — secret values are encrypted with AES-256-GCM, like all service connection credentials.
  • Write-only — API responses and the dashboard never return the stored value.
  • Redaction — exact secret values are redacted from function runtime errors and console/log output.
  • Preflight — secrets resolve before user code runs; an unbound alias fails fast instead of executing with a missing credential.
  • Deletion protection — a secret connection still bound to a function cannot be deleted (the API returns 409 Conflict) until it is unbound or the import is removed.
  • Account isolation — secrets can only be bound to functions in the same account.

Secret values are limited to 64 KiB.

Next steps

Did this page help you?