Webhooks
Receive real-time notifications when events occur in your ProposeFlow account. Webhooks let you integrate with your existing systems and trigger automated workflows.
Available Events
| Event | Description |
|---|---|
proposal.created | A new proposal was generated |
proposal.approved | A proposal was approved by a user |
proposal.rejected | A proposal was rejected by a user |
proposal.expired | A proposal expired without a decision |
event.completed | An event was processed successfully |
event.failed | An event failed to process |
proposals.generated | Proposals were generated from an event |
Creating a Webhook
create-webhook.ts
const webhook = await pf.webhooks.create({
url: 'https://your-app.com/api/webhooks/proposeflow',
events: ['proposal.approved', 'proposal.rejected'],
secret: 'whsec_your_secret_key', // For signature verification
});
console.log(webhook.id); // 'wh_abc123'Webhook Payload
Webhook payloads include the event type, timestamp, and relevant data.
payload.json
{
"id": "evt_abc123",
"type": "proposal.approved",
"created": "2024-01-15T10:30:00Z",
"data": {
"proposal": {
"id": "prop_xyz789",
"schemaId": "blogPost",
"status": "approved",
"data": {
"title": "Understanding TypeScript",
"content": "TypeScript is a typed superset...",
"tags": ["typescript", "programming"]
},
"decidedAt": "2024-01-15T10:30:00Z",
"decidedBy": "user_123"
}
}
}event.completed Payload
event-completed.json
{
"id": "evt_abc123",
"type": "event.completed",
"created": "2024-01-15T10:30:00Z",
"data": {
"eventId": "pf_evt_abc123",
"status": "skipped",
"proposalCount": 0,
"cacheHit": true,
"cacheKey": "b2b8c7b1e9c84c3e1c4f4a5a7e9b9c3f1fcd2e0b9b1e7b8a3b5f64c1d2a9e7f0",
"coalescedEventIds": ["pf_evt_old1", "pf_evt_old2"]
}
}Verifying Signatures
Always verify webhook signatures to ensure requests are authentic. ProposeFlow signs payloads with HMAC-SHA256.
verify.ts
import { verifyWebhookSignature } from '@proposeflow/sdk';
app.post('/api/webhooks/proposeflow', async (req, res) => {
const signature = req.headers['x-proposeflow-signature'];
const timestamp = req.headers['x-proposeflow-timestamp'];
const body = req.rawBody; // Must be raw body, not parsed JSON
const isValid = verifyWebhookSignature({
payload: body,
signature,
timestamp,
secret: process.env.WEBHOOK_SECRET!,
});
if (!isValid) {
return res.status(401).send('Invalid signature');
}
// Process the webhook
const event = JSON.parse(body);
// ...
res.status(200).send('OK');
});Next.js API Route Example
app/api/webhooks/proposeflow/route.ts
import { verifyWebhookSignature } from '@proposeflow/sdk';
import { NextRequest, NextResponse } from 'next/server';
export async function POST(req: NextRequest) {
const body = await req.text();
const signature = req.headers.get('x-proposeflow-signature')!;
const timestamp = req.headers.get('x-proposeflow-timestamp')!;
const isValid = verifyWebhookSignature({
payload: body,
signature,
timestamp,
secret: process.env.WEBHOOK_SECRET!,
});
if (!isValid) {
return NextResponse.json({ error: 'Invalid signature' }, { status: 401 });
}
const event = JSON.parse(body);
switch (event.type) {
case 'proposal.approved':
await handleApproval(event.data.proposal);
break;
case 'proposal.rejected':
await handleRejection(event.data.proposal);
break;
}
return NextResponse.json({ received: true });
}Retry Behavior
ProposeFlow automatically retries failed webhook deliveries with exponential backoff.
- •Webhooks are retried up to 5 times
- •Retry delays: 1min, 5min, 30min, 2hr, 24hr
- •A 2xx response is considered successful
- •Requests timeout after 30 seconds
Managing Webhooks
manage.ts
// List all webhooks
const webhooks = await pf.webhooks.list();
// Update a webhook
await pf.webhooks.update('wh_abc123', {
events: ['proposal.approved'], // Only receive approvals
});
// Disable a webhook
await pf.webhooks.update('wh_abc123', {
enabled: false,
});
// Delete a webhook
await pf.webhooks.delete('wh_abc123');