How to Configure Webhooks for Prompt Lifecycle Events in prompts.chat

prompts.chat includes a built-in webhook system that sends asynchronous HTTP notifications to external endpoints whenever prompts are created, updated, or deleted, featuring configurable payload templates, placeholder interpolation, and SSRF protection.

The prompts.chat repository provides a complete webhook infrastructure for monitoring prompt lifecycle changes. By configuring webhooks through the admin interface or REST API, you can integrate external services like Slack, Discord, or custom CI pipelines to react immediately when content changes. This guide explains the architecture, configuration options, and security mechanisms implemented in the source code.

Webhook Architecture Overview

The system consists of three distinct layers working together to deliver reliable event notifications.

The Admin API layer, located in src/app/api/admin/webhooks/route.ts, handles CRUD operations for webhook configurations, input validation via validateWebhook (lines 20-76), and SSRF protection through the isPrivateUrl check. This layer ensures only valid, publicly reachable endpoints can be registered.

The Webhook Engine in src/lib/webhook.ts manages the core execution logic. It queries the Prisma webhookConfig table for active webhooks matching specific events, interpolates placeholder tokens using replacePlaceholders (lines 23-56), and dispatches HTTP requests asynchronously in fire-and-forget mode.

The React UI layer in src/components/admin/webhooks-table.tsx provides the administrative interface for creating, editing, and testing webhooks, including a visual placeholder picker that reads from the WEBHOOK_PLACEHOLDERS definition.

Configuring Webhook Endpoints

To create a webhook configuration, send a POST request to the admin endpoint or use the web interface. The system validates the payload structure and rejects private network addresses to prevent SSRF attacks.

The configuration object stored in the database via db.webhookConfig.create includes the following fields:

  • name: Descriptive identifier for the webhook
  • url: Target endpoint (must pass isPrivateUrl validation)
  • method: HTTP verb (GET, POST, PUT, or PATCH)
  • headers: Optional JSON object for custom request headers
  • payload: Template string with placeholders for dynamic data
  • events: Array specifying which lifecycle events trigger the webhook
  • isEnabled: Boolean toggle to activate or deactivate the webhook

When a prompt changes, the prompt API routes call triggerWebhooks(event, data) with the appropriate event type and prompt data. For example, in src/app/api/prompts/route.ts (lines 41-55), the creation handler executes:

triggerWebhooks("PROMPT_CREATED", { /* PromptData */ });

Similar calls exist for update and delete operations in their respective route handlers.

Supported Lifecycle Events

The webhook system supports three primary event types that correspond to CRUD operations on prompts:

  • PROMPT_CREATED: Fires immediately after a new prompt is persisted to the database
  • PROMPT_UPDATED: Fires when an existing prompt's metadata or content is modified
  • PROMPT_DELETED: Fires when a prompt is removed from the system

You can configure a single webhook to listen to multiple events or create separate webhooks for different notification channels.

Payload Templates and Placeholders

The payload template system uses string interpolation to inject runtime prompt data. Placeholders defined in WEBHOOK_PLACEHOLDERS (lines 24-41 of src/lib/webhook.ts) include:

Placeholder Value Injected
{{PROMPT_ID}} UUID of the prompt
{{PROMPT_TITLE}} Escaped title string
{{PROMPT_DESCRIPTION}} Description text or "No description"
{{PROMPT_CONTENT}} Body text truncated to 2000 characters
{{PROMPT_TYPE}} Prompt category enum value
{{PROMPT_URL}} Public URL (https://prompts.chat/prompts/<id>)
{{AUTHOR_USERNAME}} Creator's handle
{{AUTHOR_NAME}} Display name or username fallback
{{AUTHOR_AVATAR}} Avatar URL or default image
{{CATEGORY_NAME}} Assigned category or "Uncategorized"
{{TAGS}} Comma-separated tag list
{{TIMESTAMP}} Human-readable creation time
{{SITE_URL}} Base application URL from NEXT_PUBLIC_APP_URL
{{CHATGPT_URL}} Direct link to execute in ChatGPT

The admin UI automatically populates the placeholder picker by reading Object.values(WEBHOOK_PLACEHOLDERS) from the configuration.

Security and Validation

The system implements multiple safeguards to prevent abuse and ensure reliable delivery.

SSRF Protection: The isPrivateUrl function validates that target URLs are not loopback addresses, private IP ranges, or internal network hosts. This check runs both during configuration creation and immediately before dispatch to prevent DNS rebinding attacks.

Asynchronous Execution: Webhooks fire in fire-and-forget mode using fetch. Delivery failures are logged to the console but do not block the user-facing prompt operations or return errors to the end user.

Input Sanitization: The validateWebhook function in the admin route (lines 20-76) enforces schema validation on incoming configuration requests, ensuring URLs are well-formed and event arrays contain only valid enum values.

Testing Your Configuration

Before relying on webhooks in production, you can verify connectivity and payload formatting using the built-in test endpoint.

Send a POST request to /api/admin/webhooks/[id]/test, where [id] is the webhook configuration UUID. This endpoint, defined in src/app/api/admin/webhooks/[id]/test/route.ts (lines 28-53), constructs a synthetic payload using the same replacePlaceholders logic and transmits a single request to your endpoint.

The test returns a JSON response indicating success or detailed error information, allowing you to debug template syntax or connectivity issues without modifying live prompt data.

Implementation Examples

Creating a Slack Notification Webhook

await fetch("/api/admin/webhooks", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    name: "Slack Prompt Alerts",
    url: "https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX",
    method: "POST",
    headers: { "Content-Type": "application/json" },
    payload: JSON.stringify({
      text: "New prompt created: {{PROMPT_TITLE}}",
      blocks: [
        { 
          type: "section", 
          text: { 
            type: "mrkdwn", 
            text: "Author: {{AUTHOR_NAME}}\nContent: {{PROMPT_CONTENT}}" 
          } 
        }
      ]
    }),
    events: ["PROMPT_CREATED", "PROMPT_UPDATED"],
    isEnabled: true
  })
});

Triggering a Manual Test

const testWebhook = async (webhookId: string) => {
  const response = await fetch(`/api/admin/webhooks/${webhookId}/test`, {
    method: "POST"
  });
  return response.json(); // Returns { success: boolean, error?: string }
};

Custom Placeholder Extension

To add custom tokens, modify src/lib/webhook.ts:

// Add to WEBHOOK_PLACEHOLDERS (lines 24-41)
export const WEBHOOK_PLACEHOLDERS = {
  // ... existing placeholders
  "{{CUSTOM_FIELD}}": "customField"
};

// Update replacePlaceholders (lines 23-56) to handle the new mapping
const valueMap: Record<string, string> = {
  "{{PROMPT_ID}}": data.id,
  "{{CUSTOM_FIELD}}": data.customField,
  // ... other mappings
};

Summary

  • prompts.chat provides a three-layer webhook architecture separating admin configuration, engine execution, and UI management.
  • Webhooks support three lifecycle events: PROMPT_CREATED, PROMPT_UPDATED, and PROMPT_DELETED.
  • Configuration requires admin privileges and passes through validateWebhook and isPrivateUrl checks to prevent SSRF.
  • Payload templates support 13+ placeholders including prompt metadata, author details, and direct ChatGPT links.
  • The system executes webhooks asynchronously without blocking user operations, logging errors to the server console.
  • Use the /api/admin/webhooks/[id]/test endpoint to verify configurations before production deployment.

Frequently Asked Questions

How do I prevent webhooks from firing on private network addresses?

The system automatically blocks private URLs through the isPrivateUrl validation function, which checks both during configuration creation in src/app/api/admin/webhooks/route.ts and immediately before dispatch in src/lib/webhook.ts. This prevents Server-Side Request Forgery (SSRF) attacks against internal services like localhost, 10.0.0.0/8, or 192.168.0.0/16.

Can I modify the webhook payload structure for different services?

Yes, the payload field accepts any valid JSON string with embedded placeholders. The system uses replacePlaceholders (lines 23-56) to substitute tokens like {{PROMPT_TITLE}} before transmission. You can create custom templates for Slack, Discord, Microsoft Teams, or proprietary APIs by adjusting the JSON structure and headers accordingly.

What happens if a webhook endpoint is down or returns an error?

Webhook delivery failures are caught and logged to the server console via console.error, but they never block the prompt creation, update, or deletion flow. The system operates in fire-and-forget mode, meaning it does not implement retry logic or dead-letter queues. For critical integrations, implement idempotent receiving endpoints and handle retries on the consumer side.

Which database table stores the webhook configurations?

Configurations persist in the webhookConfig table managed by Prisma ORM. The schema stores name, url, method, headers, payload template, events array, and the isEnabled boolean flag. You can query this directly for administrative audits or backup purposes, though all CRUD operations should route through the /api/admin/webhooks endpoints to maintain validation and security checks.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →