# Receiving Messages & Webhooks

Description: Set up one public webhook endpoint, verify Surge signatures, and dispatch events safely.
Related: /docs/receiving/events, /docs/receiving/two-way, /api-reference/webhooks/signature-validation, /resources/deprecations, /sdks/typescript

Use this page when the task is to accept inbound events from Surge in an application server.

## Handler Contract

- expose one public endpoint URL
- verify the signature before trusting the body
- dispatch by `payload.type`
- make the handler idempotent
- return `2xx` quickly and queue slow work

## Endpoint Requirements

- public `https://` URL preferred
- cannot resolve to private IP space or reserved local-only TLDs
- must be reachable from Surge over the public internet
- should tolerate retries and duplicate deliveries

For development, tunnel your local server instead of using `localhost`.

## Signature Verification

Headers on every request:

- `webhook-id`
- `webhook-timestamp`
- `webhook-signature`

Signed payload format:

```text
{webhook-id}.{webhook-timestamp}.{raw-body}
```

Secret format:

- dashboard values start with `whsec_`
- strip the prefix
- base64-decode the remaining bytes before using them as the HMAC key

Prefer the `standardwebhooks` library in Python, TypeScript, or Ruby. In Elixir or unsupported runtimes, compute the HMAC manually over the raw request body bytes.

## Implementation Rules

- preserve the raw request body until signature verification is complete
- never verify against parsed/re-serialized JSON
- treat `surge-signature` as deprecated; use `webhook-signature`
- reject stale timestamps to limit replay attacks

## Minimum Event Handling

- `message.received`: create or continue the inbound workflow
- `contact.opted_out`: suppress future outbound sends on that conversation
- `message.failed`: record `failure_reason`
- `campaign.approved`: note that approval happened, but do not assume numbers are ready yet
- `phone_number.attached_to_campaign`: mark that number production-ready

## Verification Checklist

- invalid signatures return `400` or another non-`2xx`
- valid signatures reach your dispatch switch
- the endpoint returns `200` quickly for handled events
- duplicate deliveries do not create duplicate side effects

## Follow-On Reads

- `/docs/receiving/events.md` for the event taxonomy
- `/docs/receiving/two-way` for reply workflows
- `/api-reference/webhooks/signature-validation` for the detailed header semantics
