Skip to main content

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 scheduled
  • post.delivered - A post has been successfully published to all platforms
  • post.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/json
  • Flow-Signature: t=1703123456,v1=abc123... - HMAC-SHA256 signature for verification
  • Flow-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

  1. 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)
  2. Use HTTPS - Webhook URLs must use HTTPS in production

    • Flow blocks HTTP webhook URLs in production
    • Prevents man-in-the-middle attacks
  3. Validate webhook URLs - Flow validates webhook URLs to prevent SSRF attacks

    • Blocks internal IP addresses
    • Blocks localhost and private IP ranges

Performance

  1. Respond quickly - Your endpoint should respond within 5 seconds

    • Flow times out webhook deliveries after 30 seconds
    • Slow endpoints cause retries and increase load
  2. 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

  1. Log everything - Log all webhook deliveries for debugging

    • Log successful deliveries
    • Log failed deliveries with error details
    • Log signature verification failures
  2. Monitor delivery status - Check delivery history regularly for failed deliveries

    • Use GET /v1/webhooks/{id}/deliveries to check status
    • Set up alerts for high failure rates
  3. Implement retry logic - If your endpoint is temporarily unavailable

    • Flow automatically retries with exponential backoff
    • Ensure your endpoint can handle retries gracefully

Testing

  1. Test webhook endpoints - Use the test endpoint to verify configuration
    • POST /v1/webhooks/{id}/test sends 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