Skip to main content

Webhooks

Receive real-time notifications about events in your PromptReports workspace. Configure webhooks to automate workflows and keep your systems in sync.

Webhooks Overview#

Webhooks allow you to receive real-time HTTP notifications when events occur in your PromptReports workspace. Instead of polling the API for changes, webhooks push updates to your application as they happen.

Real-Time Updates

Receive instant notifications when prompts are updated, reports are generated, or evaluations complete.

Secure Delivery

All webhook payloads are signed with HMAC-SHA256 for verification. Supports HTTPS only.

Automatic Retries

Failed deliveries are automatically retried with exponential backoff for up to 24 hours.

Flexible Configuration

Subscribe to specific event types and filter by resource or organization.

Configuration#

Configure webhooks through the API or the PromptReports dashboard. Each webhook endpoint can subscribe to multiple event types.

1

Create a Webhook Endpoint

Set up an HTTPS endpoint in your application to receive webhook events. The endpoint must respond with a 2xx status code within 30 seconds.
2

Register the Webhook

Use the API or dashboard to register your endpoint URL and select the event types you want to receive.
3

Store the Signing Secret

Save the webhook signing secret securely. You'll use this to verify that incoming requests are from PromptReports.
4

Implement Signature Verification

Verify the signature of each incoming webhook request to ensure authenticity and prevent spoofing.
Create Webhook via API
bash
curl -X POST "https://api.promptreports.ai/v1/webhooks" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://yourapp.com/webhooks/promptreports",
    "events": [
      "prompt.created",
      "prompt.updated",
      "report.completed",
      "evaluation.completed"
    ],
    "description": "Production webhook endpoint"
  }'
Webhook Registration Response
json
{
  "id": "wh_abc123",
  "url": "https://yourapp.com/webhooks/promptreports",
  "events": [
    "prompt.created",
    "prompt.updated",
    "report.completed",
    "evaluation.completed"
  ],
  "secret": "whsec_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "status": "active",
  "createdAt": "2024-01-15T10:30:00Z"
}

Payload Format#

All webhook payloads follow a consistent JSON format with event metadata and the relevant resource data.

Webhook Payload Structure
json
{
  "id": "evt_xyz789",
  "type": "prompt.updated",
  "apiVersion": "2024-01-15",
  "createdAt": "2024-01-15T14:32:10Z",
  "data": {
    "object": "prompt",
    "id": "prm_abc123",
    "name": "Customer Support Response",
    "content": "You are a helpful customer support agent...",
    "folderId": "fld_def456",
    "version": 3,
    "updatedAt": "2024-01-15T14:32:10Z",
    "updatedBy": "user_ghi789"
  },
  "metadata": {
    "organizationId": "org_jkl012",
    "workspaceId": "ws_mno345",
    "triggeredBy": "api"
  }
}
FieldTypeDescription
idstringUnique identifier for this event (use for idempotency)
typestringThe event type (e.g., prompt.updated)
apiVersionstringAPI version used to generate the payload
createdAtstringISO 8601 timestamp of when the event occurred
dataobjectThe resource object that triggered the event
data.objectstringType of the resource (prompt, report, etc.)
metadataobjectAdditional context about the event

Signature Verification#

Every webhook request includes a signature header that you must verify to ensure the request is authentic. The signature is computed using HMAC-SHA256 with your webhook secret.

Signature Headers
text
X-PromptReports-Signature: t=1673456789,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
X-PromptReports-Timestamp: 1673456789

The signature header contains a timestamp (t) and a signature (v1). Use both to verify the request.

Signature Verification (Node.js)
typescript
import crypto from 'crypto';

function verifyWebhookSignature(
  payload: string,
  signature: string,
  secret: string
): boolean {
  // Parse the signature header
  const parts = signature.split(',');
  const timestamp = parts.find(p => p.startsWith('t='))?.split('=')[1];
  const expectedSig = parts.find(p => p.startsWith('v1='))?.split('=')[1];

  if (!timestamp || !expectedSig) {
    return false;
  }

  // Verify timestamp is within tolerance (5 minutes)
  const currentTime = Math.floor(Date.now() / 1000);
  if (Math.abs(currentTime - parseInt(timestamp)) > 300) {
    console.error('Webhook timestamp outside tolerance window');
    return false;
  }

  // Compute expected signature
  const signedPayload = `${timestamp}.${payload}`;
  const computedSig = crypto
    .createHmac('sha256', secret)
    .update(signedPayload)
    .digest('hex');

  // Constant-time comparison to prevent timing attacks
  return crypto.timingSafeEqual(
    Buffer.from(expectedSig),
    Buffer.from(computedSig)
  );
}

// Express.js middleware example
app.post('/webhooks/promptreports', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.headers['x-promptreports-signature'] as string;
  const payload = req.body.toString();

  if (!verifyWebhookSignature(payload, signature, process.env.WEBHOOK_SECRET!)) {
    return res.status(401).json({ error: 'Invalid signature' });
  }

  const event = JSON.parse(payload);
  // Process the event...

  res.status(200).json({ received: true });
});
Signature Verification (Python)
python
import hmac
import hashlib
import time

def verify_webhook_signature(payload: bytes, signature: str, secret: str) -> bool:
    # Parse signature header
    parts = dict(p.split('=') for p in signature.split(','))
    timestamp = parts.get('t')
    expected_sig = parts.get('v1')

    if not timestamp or not expected_sig:
        return False

    # Verify timestamp (5 minute tolerance)
    if abs(time.time() - int(timestamp)) > 300:
        return False

    # Compute signature
    signed_payload = f"{timestamp}.{payload.decode('utf-8')}"
    computed_sig = hmac.new(
        secret.encode('utf-8'),
        signed_payload.encode('utf-8'),
        hashlib.sha256
    ).hexdigest()

    # Constant-time comparison
    return hmac.compare_digest(expected_sig, computed_sig)

Event Types#

Subscribe to specific event types based on your integration needs. Events are organized by resource type.

Prompt Events#

EventDescriptionTrigger
prompt.createdA new prompt was createdAPI, Dashboard
prompt.updatedA prompt's content or settings changedAPI, Dashboard
prompt.deletedA prompt was deletedAPI, Dashboard
prompt.version.createdA new version was publishedAPI, Dashboard
prompt.executedA prompt was executed via APIAPI

Report Events#

EventDescriptionTrigger
report.createdReport generation startedAPI, Dashboard, Automation
report.completedReport generation finished successfullySystem
report.failedReport generation failedSystem
report.exportedReport was exported to PDF/WordAPI, Dashboard

Evaluation Events#

EventDescriptionTrigger
evaluation.startedEvaluation run startedAPI, Dashboard
evaluation.completedAll test cases completedSystem
evaluation.failedEvaluation encountered an errorSystem
evaluation.test.completedIndividual test case completedSystem

Organization Events#

EventDescriptionTrigger
member.invitedA new member was invitedDashboard
member.joinedAn invited member acceptedDashboard
member.removedA member was removedDashboard
subscription.updatedSubscription plan changedBilling

Retry Logic#

When webhook delivery fails, PromptReports automatically retries with exponential backoff. Failed events are retried for up to 24 hours.

AttemptDelayCumulative Time
1Immediate0 seconds
21 minute1 minute
35 minutes6 minutes
430 minutes36 minutes
52 hours2.5 hours
64 hours6.5 hours
78 hours14.5 hours
812 hours24+ hours (final)

A delivery is considered successful when your endpoint returns a 2xx HTTP status code within 30 seconds. The following responses trigger retries:

  • HTTP status codes 408, 429, 500, 502, 503, 504
  • Connection timeout (30 seconds)
  • DNS resolution failure
  • TLS/SSL handshake failure
Implementing Idempotency
typescript
const processedEvents = new Set<string>(); // Use Redis/database in production

app.post('/webhooks/promptreports', async (req, res) => {
  const event = req.body;

  // Check for duplicate delivery
  if (processedEvents.has(event.id)) {
    console.log(`Skipping duplicate event: ${event.id}`);
    return res.status(200).json({ received: true, duplicate: true });
  }

  // Process the event
  try {
    await processEvent(event);
    processedEvents.add(event.id);
    res.status(200).json({ received: true });
  } catch (error) {
    // Return 500 to trigger retry
    res.status(500).json({ error: 'Processing failed' });
  }
});

Best Practices#

Respond Quickly

Return 200 immediately, then process asynchronously. Avoid timeouts by offloading heavy work.

Implement Idempotency

Track processed event IDs to handle duplicate deliveries gracefully.

Verify Signatures

Always verify webhook signatures to prevent spoofed requests.

Monitor Failures

Set up alerting for repeated delivery failures. Check the webhook dashboard for issues.

Async Processing Pattern
typescript
import { Queue } from 'bullmq';

const webhookQueue = new Queue('webhook-processing');

app.post('/webhooks/promptreports', async (req, res) => {
  // Verify signature first
  if (!verifySignature(req)) {
    return res.status(401).json({ error: 'Invalid signature' });
  }

  const event = req.body;

  // Immediately acknowledge receipt
  res.status(200).json({ received: true });

  // Queue for async processing
  await webhookQueue.add('process-event', {
    eventId: event.id,
    eventType: event.type,
    payload: event,
  }, {
    attempts: 3,
    backoff: { type: 'exponential', delay: 1000 },
  });
});

// Process events asynchronously
webhookQueue.process('process-event', async (job) => {
  const { eventType, payload } = job.data;

  switch (eventType) {
    case 'prompt.updated':
      await handlePromptUpdate(payload.data);
      break;
    case 'report.completed':
      await handleReportComplete(payload.data);
      break;
    // Handle other event types...
  }
});

Testing Webhooks#

Test your webhook implementation before going to production. PromptReports provides several tools to help you validate your setup.

Send Test Event via API
bash
curl -X POST "https://api.promptreports.ai/v1/webhooks/wh_abc123/test" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "eventType": "prompt.updated"
  }'
View Recent Deliveries
bash
curl -X GET "https://api.promptreports.ai/v1/webhooks/wh_abc123/deliveries" \
  -H "Authorization: Bearer YOUR_API_KEY"

# Response includes delivery status, response codes, and timing
{
  "deliveries": [
    {
      "id": "del_xyz789",
      "eventId": "evt_abc123",
      "eventType": "prompt.updated",
      "status": "success",
      "httpStatus": 200,
      "responseTime": 245,
      "attemptNumber": 1,
      "deliveredAt": "2024-01-15T14:32:15Z"
    }
  ]
}
Local Testing with ngrok
bash
# Start your local server
npm run dev  # Running on http://localhost:3000

# In another terminal, create a tunnel
ngrok http 3000

# Use the ngrok URL for your webhook endpoint
# Example: https://abc123.ngrok.io/webhooks/promptreports