How standard webhooks work
For workflows without an event_name, ModelRiver sends the complete AI response to your webhook endpoint immediately after processing. This is a simple fire-and-forget pattern: your backend receives the result and can process it as needed.
Webhook payload structure
JSON
1{2 "type": "task.completed",3 "workflow": "customer-support-summary",4 "status": "success",5 "channel_id": "550e8400-e29b-41d4-a716-446655440000",6 "data": {7 "summary": "Customer requested refund for order #12345...",8 "sentiment": "negative",9 "category": "billing"10 },11 "meta": {12 "provider": "openai",13 "model": "gpt-4o",14 "tokens": {15 "prompt": 245,16 "completion": 89,17 "total": 33418 },19 "duration_ms": 2341,20 "attempts": [21 {22 "provider": "openai",23 "model": "gpt-4o",24 "duration_ms": 2341,25 "success": true26 }27 ]28 },29 "customer_data": {30 "user_id": "user_789",31 "session_id": "sess_abc123"32 },33 "timestamp": "2026-01-05T12:34:56.789Z"34}Headers sent with webhooks
| Header | Description |
|---|---|
mr-signature | HMAC-SHA256 signature of the payload |
mr-timestamp | Unix timestamp when the webhook was sent |
mr-channel-id | Unique identifier for this request |
content-type | Always application/json |
Integration examples
Node.js (Express)
JAVASCRIPT
1app.post('/webhooks/ai', (req, res) => {2 const { type, data, customer_data } = req.body;3 4 // Your business logic here5 console.log('AI completed:', data);6 7 res.status(200).json({ received: true });8});Python (Flask)
PYTHON
1@app.route('/webhooks/ai', methods=['POST'])2def handle_webhook():3 payload = request.json4 event_type = payload['type']5 data = payload['data']6 7 # Your business logic here8 print(f'AI completed: {data}')9 10 return jsonify({'received': True}), 200Next steps
- Signature verification: Verify webhook authenticity
- Event-driven workflows: Add custom processing before final delivery
- Delivery & retries: Understand retry policies