# Webhooks Photon methods can receive HTTP webhook callbacks from external services. Webhook methods are exposed as POST endpoints on the daemon. ## URL Pattern ``` POST /webhook/{photonName}/{method} ``` For example, a photon named `forms` with a method `handleSubmission` is reachable at: ``` POST /webhook/forms/handleSubmission ``` ## Declaration ### `handle*` Prefix Any method starting with `handle` is automatically treated as a webhook handler: ```typescript async handleStripeEvent(params: { type: string; data: object }) { // Automatically exposed as POST /webhook/{photon}/handleStripeEvent } ``` ### `@webhook` JSDoc Tag Explicitly mark a method as a webhook handler: ```typescript /** * Process form submission * @webhook */ async processForm(params: { email: string; name: string }) { // Exposed as POST /webhook/{photon}/processForm } ``` With a custom path: ```typescript /** * GitHub push events * @webhook github/push */ async onPush(params: { ref: string; commits: object[] }) { // Exposed at the custom path } ``` The `@webhook` tag takes precedence over the `handle*` prefix when both are present. ## Webhook Metadata Webhook methods receive HTTP context via the `_webhook` metadata field on the params object: ```typescript interface WebhookMetadata { method: string; // HTTP method (always 'POST') headers: Record; // Request headers query: Record; // URL query parameters timestamp: number; // Unix timestamp of request receipt } ``` Access it in your handler: ```typescript async handleEvent(params: { type: string; _webhook: WebhookMetadata }) { const signature = params._webhook.headers['x-hub-signature-256']; const receivedAt = new Date(params._webhook.timestamp); // ... } ``` ## Authentication Set the `PHOTON_WEBHOOK_SECRET` environment variable to require authentication on all webhook endpoints: ```bash export PHOTON_WEBHOOK_SECRET=whsec_abc123... ``` Callers must include the secret in the `X-Webhook-Secret` header: ```bash curl -X POST https://example.com/webhook/forms/handleSubmission \ -H "X-Webhook-Secret: whsec_abc123..." \ -H "Content-Type: application/json" \ -d '{"email": "user@example.com", "name": "Alice"}' ``` Requests without a valid secret receive a `401 Unauthorized` response. ## Example: Form Submission Handler ```typescript import { Photon } from '@portel/photon-core'; class FormHandler extends Photon { /** * Handle contact form submission * @webhook * @param email Submitter email * @param name Submitter name * @param message Form message */ async handleContactForm(params: { email: string; name: string; message: string; _webhook: { method: string; headers: Record; query: Record; timestamp: number }; }) { // Verify the request came recently const age = Date.now() - params._webhook.timestamp; if (age > 60_000) { throw new Error('Request too old'); } await this.saveSubmission({ email: params.email, name: params.name, message: params.message, receivedAt: new Date(params._webhook.timestamp), }); return { success: true }; } } ```