Webhooks
Webhooks allow you to receive real-time notifications when events occur in your Flow account. This is useful for building integrations, monitoring post delivery, and triggering actions in your own systems.
Supported Events
post.created- A new post has been scheduledpost.delivered- A post has been successfully published to all platformspost.failed- A post failed to publish (will be retried)post.blocked- A post was blocked (e.g., due to rate limits or content policy)
Creating a Webhook
curl -X POST https://api.flowsocial.app/v1/webhooks \
-H "Authorization: Bearer flow_sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/webhooks/flow",
"events": ["post.delivered", "post.failed"]
}'
Response:
{
"id": "webhook_abc123",
"userId": "user_xyz",
"url": "https://example.com/webhooks/flow",
"events": ["post.delivered", "post.failed"],
"secret": "whsec_abc123...",
"active": true,
"createdAt": 1703123456000
}
Important: Save the secret - it's shown only once! You'll need it to verify webhook signatures.
Webhook Payload
When an event occurs, Flow sends a POST request to your webhook URL with the following payload:
{
"event": "post.delivered",
"data": {
"post_id": "post_abc123",
"channel_id": "channel_xyz",
"delivered_at": 1703123456000
},
"timestamp": 1703123456000
}
Webhook Headers
Every webhook request includes these headers:
Content-Type: application/jsonFlow-Signature: t=1703123456,v1=abc123...- HMAC-SHA256 signature for verificationFlow-Event-Type: post.delivered- The event type
Verifying Webhook Signatures
Always verify webhook signatures to ensure requests are from Flow and haven't been tampered with.
TypeScript Example
import crypto from 'crypto';
function verifyWebhookSignature(
payload: string,
signature: string,
secret: string
): boolean {
// Parse signature: "t=1703123456,v1=abc123..."
const parts = signature.split(',');
const timestampPart = parts.find(p => p.startsWith('t='));
const signaturePart = parts.find(p => p.startsWith('v1='));
if (!timestampPart || !signaturePart) {
return false;
}
const timestamp = timestampPart.split('=')[1];
const receivedSignature = signaturePart.split('=')[1];
// Compute expected signature
const hmac = crypto.createHmac('sha256', secret);
hmac.update(`${timestamp}.${payload}`);
const expectedSignature = hmac.digest('hex');
// Constant-time comparison
return crypto.timingSafeEqual(
Buffer.from(expectedSignature),
Buffer.from(receivedSignature)
);
}
// Usage
app.post('/webhooks/flow', (req, res) => {
const signature = req.headers['flow-signature'];
const payload = JSON.stringify(req.body);
const secret = process.env.FLOW_WEBHOOK_SECRET;
if (!verifyWebhookSignature(payload, signature, secret)) {
return res.status(401).send('Invalid signature');
}
// Process webhook
const { event, data } = req.body;
console.log(`Received event: ${event}`, data);
res.status(200).send('OK');
});
Python Example
import hmac
import hashlib
import time
def verify_webhook_signature(payload: str, signature: str, secret: str) -> bool:
# Parse signature: "t=1703123456,v1=abc123..."
parts = signature.split(',')
timestamp_part = next((p for p in parts if p.startswith('t=')), None)
signature_part = next((p for p in parts if p.startswith('v1=')), None)
if not timestamp_part or not signature_part:
return False
timestamp = timestamp_part.split('=')[1]
received_signature = signature_part.split('=')[1]
# Compute expected signature
message = f"{timestamp}.{payload}"
expected_signature = hmac.new(
secret.encode(),
message.encode(),
hashlib.sha256
).hexdigest()
# Constant-time comparison
return hmac.compare_digest(expected_signature, received_signature)
# Usage (Flask example)
@app.route('/webhooks/flow', methods=['POST'])
def webhook():
signature = request.headers.get('Flow-Signature')
payload = json.dumps(request.json)
secret = os.environ.get('FLOW_WEBHOOK_SECRET')
if not verify_webhook_signature(payload, signature, secret):
return 'Invalid signature', 401
# Process webhook
event = request.json['event']
data = request.json['data']
print(f"Received event: {event}", data)
return 'OK', 200
Retry Logic
Flow automatically retries failed webhook deliveries with exponential backoff:
- Attempt 1: Immediate
- Attempt 2: 1 second delay
- Attempt 3: 2 seconds delay
- Attempt 4: 4 seconds delay
- Attempt 5: 8 seconds delay
- Attempt 6: 16 seconds delay
- Attempt 7: 32 seconds delay
After 7 failed attempts, the webhook delivery is marked as failed and won't be retried automatically. You can view delivery history via the API:
curl https://api.flowsocial.app/v1/webhooks/{webhook_id}/deliveries \
-H "Authorization: Bearer flow_sk_live_..."
Testing Webhooks
You can test your webhook configuration by sending a test event:
curl -X POST https://api.flowsocial.app/v1/webhooks/{webhook_id}/test \
-H "Authorization: Bearer flow_sk_live_..."
This sends a test event with event: "webhook.test" to verify your webhook URL is working.
Best Practices
Security
-
Always verify signatures - Never trust webhook requests without signature verification
- Signatures prevent replay attacks and ensure authenticity
- Use constant-time comparison to prevent timing attacks
- Check timestamp to prevent old webhook replay (5-minute tolerance)
-
Use HTTPS - Webhook URLs must use HTTPS in production
- Flow blocks HTTP webhook URLs in production
- Prevents man-in-the-middle attacks
-
Validate webhook URLs - Flow validates webhook URLs to prevent SSRF attacks
- Blocks internal IP addresses
- Blocks localhost and private IP ranges
Performance
-
Respond quickly - Your endpoint should respond within 5 seconds
- Flow times out webhook deliveries after 30 seconds
- Slow endpoints cause retries and increase load
-
Handle idempotency - The same event may be delivered multiple times (due to retries)
- Use event IDs or timestamps to detect duplicates
- Make webhook handlers idempotent (safe to process same event twice)
Reliability
-
Log everything - Log all webhook deliveries for debugging
- Log successful deliveries
- Log failed deliveries with error details
- Log signature verification failures
-
Monitor delivery status - Check delivery history regularly for failed deliveries
- Use
GET /v1/webhooks/{id}/deliveriesto check status - Set up alerts for high failure rates
- Use
-
Implement retry logic - If your endpoint is temporarily unavailable
- Flow automatically retries with exponential backoff
- Ensure your endpoint can handle retries gracefully
Testing
- Test webhook endpoints - Use the test endpoint to verify configuration
POST /v1/webhooks/{id}/testsends a test event- Verify signature verification works correctly
- Test error handling and retry behavior
Webhook Management
- List webhooks:
GET /v1/webhooks - Get webhook:
GET /v1/webhooks/{id} - Update webhook:
PATCH /v1/webhooks/{id} - Delete webhook:
DELETE /v1/webhooks/{id} - Get deliveries:
GET /v1/webhooks/{id}/deliveries - Test webhook:
POST /v1/webhooks/{id}/test