Skip to main content
Photon sends your project’s events as signed HTTP POST requests to a URL you control. Use webhooks for a backend that can accept public HTTPS requests. Use WebSocket when your process needs to receive events without a public HTTP endpoint. These pages describe how Photon sends events to your application. A successful webhook response does not mean a message reached its recipient.

Add a destination

1

Create an HTTPS receiver

Create a public HTTPS URL that accepts POST requests. Keep the request body unchanged so you can verify its signature. Photon does not follow redirects. It also blocks private network destinations, including localhost.
2

Choose the events to receive

In the dashboard, open your project’s Webhooks page and select Add a destination. Set the URL, choose a payload API version, and select the events your application handles.You can also create a destination through the API. Supply an Idempotency-Key with the request. Choose a selectable API version. Then choose events from that version’s event catalog. enabledEvents: ["*"] subscribes to all events; an empty array subscribes to none. Keep the destination enabled to receive deliveries.
3

Save the signing secret

Store the returned whsec_... secret in your server’s secret manager, for example as PHOTON_WEBHOOK_SECRET. It belongs to this destination and is different from your Photon API key.Photon returns the secret when you create the destination or rotate its secret. The API also returns it if you repeat that request with its original Idempotency-Key. Reading a destination does not return its secret. Never put the secret in browser code, source control, or logs.

Verify with Standard Webhooks

We recommend the standardwebhooks package for JavaScript and TypeScript. It verifies Photon’s signatures without custom options and supports secret rotation. Other languages can use the Standard Webhooks libraries.
This server-side function accepts a Fetch API Request. It verifies the request and returns the parsed JSON body. Call it from your HTTP route before you process the event.
If verification throws an error, return 400. Do not process the event. The library checks the signature and rejects timestamps more than five minutes in the past or future. Keep your server clock synchronized.
Verify the original body, not JSON.stringify() of a parsed object. Read it before JSON middleware reads or changes it. Pass the full signing secret to new Webhook(secret). Do not use { format: "raw" } for Photon secrets.
Verification confirms that Photon signed the request. It does not check the event schema or prevent you from processing the same event twice. Use the destination’s event catalog to interpret the body after verification.

Headers and payload

Each request has Content-Type: application/json and these headers: The signature is HMAC-SHA256 over webhook-id.webhook-timestamp.rawBody, using the Base64-decoded signing key. The library handles this format for you. The body is the public event JSON for the selected API version. Photon does not wrap it in another delivery object. Webhooks and WebSocket deliver the same public event object for the same event and API version.

Handle duplicate deliveries

Save the verified event to storage that survives process restarts. Then return any 2xx status. For ordinary event notifications, an empty 204 response is enough. Queue slow work instead of keeping the request open. Photon can deliver the same event more than once. For example, a lost response can cause Photon to retry an event you already processed. Use webhook-id to detect duplicates. Save the event ID and its queued work in one atomic operation, such as a database transaction. Both must succeed or fail together. This prevents a crash from marking an event complete without saving its work. Return 2xx for a duplicate you already accepted. If you cannot save the event temporarily, return 503 so Photon can retry.

Retries and ordering

Photon increases retry delays and adds random variation to avoid simultaneous retries. A valid Retry-After on a retryable response can delay the next attempt, within the retry limits. The default limits are four days from the first failure and 20 failed attempts. Delivery can stop earlier. Photon disables a destination when its delivery failures reach a retry limit. Fix the receiver before you re-enable the destination. Photon delivers events in order within each destination and event type. A retrying event holds up later events of that type for that destination. Other event types and destinations can continue independently. Do not assume that events arrive in order across different event types or destinations. Normal deliveries have a 20-second timeout by default. Events that request a synchronous response have a two-second initial timeout by default. For those events, return the response that the event’s contract requires before that deadline. An HTTP response to a webhook is not a new chat message. Send chat replies through the messaging API.

Rotate the secret

Rotate the signing secret and save the new value securely. During rotation, the old and new keys can be valid at the same time. Photon sends signatures for both in the same header. standardwebhooks accepts the signature that matches your configured secret. Update your receiver to use the new secret before the old key expires.

Test your receiver

Use a public HTTPS tunnel to reach a local receiver, or use WebSocket without a tunnel. Trigger an event you subscribed to and confirm that your application verifies and accepts it. Check the dashboard’s delivery logs when no request arrives or a delivery fails. When verification fails, check the destination’s secret, all three headers, your server clock, and whether middleware changed the raw body. Do not disable verification to make a test pass.