# Receive Stormedo deliveries

Source: https://stormedo.com/docs/how-to/receive-deliveries/

Receive and verify your first Stormedo delivery, then add duplicate handling before processing business events.

Use this procedure for an endpoint you control. For a third-party API, supply that API’s destination credentials through [forwarded headers](https://stormedo.com/docs/api-reference/send/#headers); you do not need to modify its server to accept Stormedo signatures.

## Choose a runnable receiver

- [Express webhook receiver](https://stormedo.com/examples/express-webhook-receiver/): Node.js, Express, and the JavaScript SDK.
- [FastAPI webhook receiver](https://stormedo.com/examples/fastapi-webhook-receiver/): Python, FastAPI, and the Python SDK.

Both examples verify the original request bytes, parse the JSON event, and print it in your terminal. They return `204` to acknowledge the delivery. You can run them without setting up a database.

## Configure the destination

1. Run the receiver with `STORMEDO_PROJECT_ID` and `PUBLIC_WEBHOOK_URL`. The project ID is the `data.project_id` returned by a successful send using your project’s API key, such as the [quickstart](https://stormedo.com/docs/get-started/first-request/).
2. Set `PUBLIC_WEBHOOK_URL` to the complete HTTPS URL you will submit to Stormedo, including its path and any query string. Use the same canonical URL in both places. Trailing slashes and query strings matter.
3. Expose the endpoint at that public URL. Stormedo cannot reach `localhost`, a private IP, or a private network. For local development, use a public HTTPS tunnel to the local receiver and configure its HTTPS URL as the expected audience.
4. Reuse one verifier per process. It retrieves public project keys and caches them. Receiving does not require your publisher API key.

Do not reconstruct the trusted audience from an arbitrary `Host`, `Forwarded`, or `X-Forwarded-*` header. A reverse proxy may terminate TLS or rewrite paths; the verifier still needs the original public destination URL.

## Process in this order

```text
raw HTTP bytes and exactly one of each signing header
  → verify signature, project, URL, method, request ID, time, and body
  → parse the JSON event
  → log the received event
  → return 204
```

Read no more than 256 KiB. Register raw-body handling before JSON middleware. Parsing and serializing JSON again changes the bytes even when the data looks identical.

The signature is short lived. Verify at ingress before putting work into another queue. If you acknowledge after writing to your own durable queue, that queue and its consumers become responsible for finishing the business operation.

## Make duplicates safe before adding business logic

The examples log each verified delivery, including repeats. Before replacing the log with a business action, validate the event fields and make repeated delivery safe.

Use the verified request ID to deduplicate attempts. Store it and the business change in the same transaction, with a unique constraint. On a duplicate, acknowledge after confirming the original transaction committed. Roll back both records if processing fails.

Replay creates a new Stormedo request ID. To deduplicate the business event across replays too, include a stable event ID in the payload and give it a unique constraint. Keep deduplication records for as long as a repeated event would be harmful, including your manual replay window.

A database transaction cannot atomically commit an external network call. For that case, use the destination API’s idempotency key or a transactional outbox. See [Idempotency](https://stormedo.com/docs/concepts/idempotency/).

## Choose the response deliberately

| Outcome                                             | Receiver response | What happens next                                                  |
| --------------------------------------------------- | ----------------- | ------------------------------------------------------------------ |
| Work committed, or a known duplicate                | `204`             | Stormedo marks delivery successful.                                |
| Missing/duplicated signing header or invalid event  | `400`             | Terminal failure; correct the publisher or receiver before replay. |
| Invalid signature, project, audience, time, or body | `401`             | Terminal failure; investigate configuration or tampering.          |
| Public-key fetch temporarily unavailable            | `503`             | Retryable if the request has attempts remaining.                   |
| Temporary database or processing failure            | `500` or `503`    | Retryable if attempts remain. Do not commit the deduplication row. |

Every `2xx` is success, including `202`. Return it only after the side effect or your own durable handoff has committed. Stormedo does not inspect your response body for an application-level error.

## Send and inspect

Submit the example’s event with at least a few attempts configured. Keep the returned `data.id`, open it in [Requests](https://app.stormedo.com/dashboard/requests), and check the completed attempt’s status code. A brief initial `404` in the read API can mean history is still catching up.

Use [delivery troubleshooting](https://stormedo.com/docs/how-to/troubleshoot-delivery/) for failures and the [signing protocol](https://stormedo.com/docs/reference/delivery-signatures/) when implementing another language.
