Skip to Content
RoutinesWebhook Triggers

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

  1. Open a routine in Town
  2. Go to the routine’s Config page
  3. Find the Webhook section
  4. Choose a default account
  5. Optionally allow account_email override
  6. Click Enable
  7. 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/json
  • text/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 alias
  • data — 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.com

Trigger account selection

Every webhook run needs a trigger account. Town resolves it in this order:

  1. Request account_email, if provided and allowed
  2. The webhook’s configured default account
  3. Your primary connected account

Important details:

  • account_email must 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_email does 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 404 responses 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-id

Town 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:

  1. Add Webhooks by Zapier as the trigger or action step that sends the request
  2. Choose POST
  3. Paste your Town webhook URL
  4. Add the Authorization header with your bearer secret
  5. Set Content-Type to application/json
  6. 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 created
  • 400 — Invalid request, including bad account_email
  • 404 — Unknown endpoint, disabled endpoint, or invalid secret
  • 413 — Payload too large
  • 415 — Unsupported content type
  • 429 — 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_email override when the caller truly needs it
  • Rotate secrets if they may have been exposed
  • Include an idempotency key when your sender can retry
Last updated on