Skip to content

Latest commit

 

History

History
134 lines (103 loc) · 3.16 KB

File metadata and controls

134 lines (103 loc) · 3.16 KB

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:

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:

/**
 * Process form submission
 * @webhook
 */
async processForm(params: { email: string; name: string }) {
  // Exposed as POST /webhook/{photon}/processForm
}

With a custom path:

/**
 * 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:

interface WebhookMetadata {
  method: string;       // HTTP method (always 'POST')
  headers: Record<string, string>;  // Request headers
  query: Record<string, string>;    // URL query parameters
  timestamp: number;    // Unix timestamp of request receipt
}

Access it in your handler:

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:

export PHOTON_WEBHOOK_SECRET=whsec_abc123...

Callers must include the secret in the X-Webhook-Secret header:

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

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<string, string>; query: Record<string, string>; 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 };
  }
}