Webhook Triggers
Webhook triggers let an external system start a routine by sending an authenticated HTTP request to a routine-specific URL.
This is the simplest way to:
- Start a routine from Zapier
- Start a routine from your own app or backend
- Send structured event data into Town for processing
How it works
Each routine can have its own webhook endpoint in Routine Settings.
When you enable it, Town creates:
- A stable webhook URL
- A secret bearer token
- A default trigger account for the run
When another system sends a valid request to that URL, Town starts a routine run using the routine’s existing tools, instructions, and mode.
Where to set it up
- Open a routine in Town
- Go to the routine’s Config page
- Find the Webhook section
- Choose a default account
- Optionally allow
account_emailoverride - Click Enable
- Copy the webhook URL and secret
The secret is shown once when you enable or rotate the webhook. Save it in your automation tool or server configuration right away.
Authentication
Webhook endpoints use bearer-token authentication.
Send the secret in the Authorization header:
Authorization: Bearer whsec_...If the endpoint is disabled, the secret is wrong, or the endpoint does not exist, Town returns the same 404 response. This helps prevent endpoint discovery.
Supported request formats
Webhook endpoints accept:
application/jsontext/plain
Town rejects other content types.
JSON requests
For JSON requests, you can optionally provide:
account_email— choose the trigger account by exact email or synced aliasdata— the payload your routine should inspect
Example:
{
"account_email": "team@company.com",
"data": {
"event": "invoice.created",
"invoice_id": "inv_123",
"amount": 4800
}
}If data is present, Town uses that as the main payload. If data is missing, Town uses the full JSON body.
Text requests
For text/plain, send the raw text body directly.
If you want to select an account, include:
X-Town-Account-Email: team@company.comTrigger account selection
Every webhook run needs a trigger account. Town resolves it in this order:
- Request
account_email, if provided and allowed - The webhook’s configured default account
- Your primary connected account
Important details:
account_emailmust match an active connected account or synced alias exactly, ignoring case- If account override is disabled, Town rejects requests that include
account_email - The selected account must still be inside the routine’s existing account scope
account_emaildoes not expand what the routine can access
What the routine receives
Town treats webhook payloads as untrusted input.
Webhook payloads are not inserted as trusted instructions. Instead, the routine starts with a fixed Town-authored message explaining that the payload is untrusted data, and includes the payload inline for the routine to inspect.
This helps prevent prompt injection from external systems.
Security model
Webhook triggers are designed for server-to-server use.
Town protects them with:
- Per-routine bearer secrets
- Secret rotation
- Rate limiting
- Request-size limits
- Generic
404responses for invalid endpoints or secrets - Untrusted-input safety rules
Webhook-triggered routines are treated similarly to incoming-email routines from a safety perspective. If a routine combines untrusted input with autonomous external sending, Town may block the webhook until the routine is made safer.
Idempotency and retries
If your sender may retry requests, include:
X-Town-Idempotency-Key: your-stable-event-idTown uses this to avoid creating duplicate runs for the same webhook delivery.
Zapier setup
You do not need a custom Zapier app. Use Webhooks by Zapier.
Typical setup:
- Add Webhooks by Zapier as the trigger or action step that sends the request
- Choose
POST - Paste your Town webhook URL
- Add the
Authorizationheader with your bearer secret - Set
Content-Typetoapplication/json - Send your event data in the JSON body
Example JSON body:
{
"data": {
"lead_name": "Pat Lee",
"company": "Northwind",
"source": "Zapier"
}
}Example curl
curl -X POST "https://your-town-url/workflow-webhooks/whk_..." \
-H "Authorization: Bearer whsec_..." \
-H "Content-Type: application/json" \
-H "X-Town-Idempotency-Key: event-123" \
-d '{
"account_email": "team@company.com",
"data": {
"event": "customer.created",
"customer_id": "cus_123"
}
}'Responses
Common responses:
202— Request accepted and routine run created400— Invalid request, including badaccount_email404— Unknown endpoint, disabled endpoint, or invalid secret413— Payload too large415— Unsupported content type429— Rate limited
Best practices
- Keep webhook payloads focused and structured
- Start with approval-required mode for routines that take actions
- Use a dedicated default account when the routine is account-specific
- Only allow
account_emailoverride when the caller truly needs it - Rotate secrets if they may have been exposed
- Include an idempotency key when your sender can retry
Related
- Triggers — Overview of all routine triggers
- Manual Configuration — Routine settings and configuration
- Security — Safety model and protections
Town