rtrvr.ai supports both inbound webhooks (receive triggers from Zapier, Make, n8n, etc.) and outbound webhooks (send results to your server when workflows complete).
Inbound Webhooks (Zapier, Make, n8n → rtrvr)
Your mcp.rtrvr.ai endpoint is already webhook-ready. Any service that can send an HTTP POST can trigger rtrvr.ai workflows—no special configuration needed.
Webhook Endpoint
POST https://mcp.rtrvr.ai
Headers:
Authorization: Bearer rtrvr_your_api_key
Content-Type: application/json
Body:
{
"tool": "planner" | "extract" | "act" | "crawl" | "replay_workflow" | ...,
"params": { ... tool-specific parameters ... },
"deviceId": "optional_device_id",
"webhookUrl": "https://your-server.com/callback" // Optional: receive results here
}Zapier Integration
Use Zapier's 'Webhooks by Zapier' action to trigger rtrvr.ai workflows from any Zapier trigger (new email, form submission, calendar event, etc.).
- Add a 'Webhooks by Zapier' action to your Zap
- Select 'POST' as the method
- Set URL to: https://mcp.rtrvr.ai
- Add header: Authorization = Bearer rtrvr_your_api_key
- Add header: Content-Type = application/json
- Set Data to your JSON payload (tool + params)
// Example Zapier payload: Extract data when a new row is added to Google Sheets
{
"tool": "extract",
"params": {
"user_input": "Extract the company name, employee count, and funding info",
"tab_urls": ["{{Google Sheets Row URL}}"]
},
"webhookUrl": "https://hooks.zapier.com/hooks/catch/123/abc/"
}Make (Integromat) Integration
Use Make's HTTP module to call rtrvr.ai:
- Add an 'HTTP > Make a request' module
- URL: https://mcp.rtrvr.ai
- Method: POST
- Headers: Authorization (Bearer token), Content-Type (application/json)
- Body type: Raw, Content type: JSON
- Request content: Your tool + params JSON
n8n Integration
// n8n HTTP Request node configuration
{
"method": "POST",
"url": "https://mcp.rtrvr.ai",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"options": {
"headers": {
"Content-Type": "application/json"
}
},
"body": {
"tool": "planner",
"params": {
"user_input": "{{ $json.task_description }}",
"tab_urls": ["{{ $json.target_url }}"]
},
"webhookUrl": "{{ $node.Webhook.url }}"
}
}Available Tools for Inbound Webhooks
| Tool | Use Case | Execution |
|---|---|---|
| planner | Complex multi-step tasks from natural language | Browser (local or cloud) |
| extract | Structured data extraction with optional schema | Browser (local or cloud) |
| act | Page interactions (click, type, navigate) | Browser (local or cloud) |
| crawl | Multi-page crawling with extraction | Browser (local or cloud) |
| replay_workflow | Re-run a previous workflow by ID or shared URL | Browser (local or cloud) |
| get_browser_tabs | List open tabs (local browser only) | Local browser |
| execute_javascript | Run JS in browser sandbox | Local browser |
Outbound Webhooks (rtrvr → Your Server)
Include a webhookUrl in your request to receive results when the workflow completes. rtrvr.ai will POST the full response to your endpoint.
Enabling Outbound Webhooks
Add webhookUrl to any API request:
curl -X POST "https://mcp.rtrvr.ai" \
-H "Authorization: Bearer rtrvr_xxx" \
-H "Content-Type: application/json" \
-d '{
"tool": "planner",
"params": {
"user_input": "Find pricing for iPhone 16 Pro on Apple.com",
"tab_urls": ["https://apple.com"]
},
"webhookUrl": "https://your-server.com/rtrvr-callback",
"webhookSecret": "your_hmac_secret"
}'Webhook Payload Format
Your endpoint will receive a POST with this structure:
{
"event": "workflow.completed",
"timestamp": "2025-01-15T12:00:00.000Z",
"requestId": "req_abc123xyz",
"success": true,
"data": {
// Tool-specific response data
"taskCompleted": true,
"output": { ... },
"extractedData": [ ... ],
"creditsUsed": 5
},
"metadata": {
"tool": "planner",
"deviceId": "dj75mmaTWP0",
"executionTime": 15234,
"creditsRemaining": 9995
},
"originalRequest": {
"tool": "planner",
"params": { ... }
}
}Error Payloads
{
"event": "workflow.failed",
"timestamp": "2025-01-15T12:00:00.000Z",
"requestId": "req_abc123xyz",
"success": false,
"error": {
"message": "Device offline: no available browser extensions",
"code": "DEVICE_UNAVAILABLE",
"details": { ... }
},
"metadata": {
"tool": "planner",
"executionTime": 1234
}
}Verifying Webhook Signatures
If you provide a webhookSecret, rtrvr.ai signs the payload with HMAC-SHA256. Verify it to ensure authenticity:
// Express.js example
import crypto from 'crypto';
app.post('/rtrvr-callback', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-rtrvr-signature'] as string;
const timestamp = req.headers['x-rtrvr-timestamp'] as string;
// Verify timestamp is recent (prevent replay attacks)
const age = Date.now() - parseInt(timestamp);
if (age > 300000) { // 5 minutes
return res.status(400).json({ error: 'Timestamp too old' });
}
// Verify HMAC signature
const payload = timestamp + '.' + req.body.toString();
const expected = crypto
.createHmac('sha256', process.env.RTRVR_WEBHOOK_SECRET!)
.update(payload)
.digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
return res.status(401).json({ error: 'Invalid signature' });
}
const data = JSON.parse(req.body.toString());
console.log('Verified webhook:', data);
res.status(200).json({ received: true });
});# Flask example
import hmac
import hashlib
import time
from flask import Flask, request, jsonify
app = Flask(__name__)
WEBHOOK_SECRET = os.environ['RTRVR_WEBHOOK_SECRET']
@app.route('/rtrvr-callback', methods=['POST'])
def handle_webhook():
signature = request.headers.get('X-Rtrvr-Signature')
timestamp = request.headers.get('X-Rtrvr-Timestamp')
# Verify timestamp
if abs(time.time() * 1000 - int(timestamp)) > 300000:
return jsonify({'error': 'Timestamp too old'}), 400
# Verify signature
payload = f"{timestamp}.{request.data.decode()}"
expected = hmac.new(
WEBHOOK_SECRET.encode(),
payload.encode(),
hashlib.sha256
).hexdigest()
if not hmac.compare_digest(signature, expected):
return jsonify({'error': 'Invalid signature'}), 401
data = request.json
print(f"Verified webhook: {data}")
return jsonify({'received': True})Retry Logic
If your endpoint fails to respond with 2xx, rtrvr.ai retries with exponential backoff:
- Attempt 1: Immediate
- Attempt 2: After 5 seconds
- Attempt 3: After 30 seconds
- Attempt 4: After 2 minutes
- Attempt 5: After 10 minutes (final)
Common Webhook Patterns
Zapier → rtrvr → Zapier (Round-trip)
Trigger a workflow from Zapier, then catch the results in another Zap:
- Zap 1: New Google Form submission → Webhooks by Zapier POST to mcp.rtrvr.ai (include webhookUrl pointing to Zap 2)
- Zap 2: Catch Hook receives results → Add row to Google Sheets
Slack Command → rtrvr → Slack Message
// Your server receives Slack slash command
app.post('/slack/commands', async (req, res) => {
const { text, response_url } = req.body;
// Acknowledge immediately (Slack requires < 3s response)
res.status(200).json({ text: '🔄 Running extraction...' });
// Trigger rtrvr.ai with Slack's response_url as webhook
await fetch('https://mcp.rtrvr.ai', {
method: 'POST',
headers: {
'Authorization': 'Bearer rtrvr_xxx',
'Content-Type': 'application/json',
},
body: JSON.stringify({
tool: 'extract',
params: {
user_input: text,
tab_urls: [extractUrlFromText(text)],
},
webhookUrl: 'https://your-server.com/slack-callback',
webhookMetadata: { response_url }, // Pass through for callback
}),
});
});
// Receive results and post to Slack
app.post('/slack-callback', async (req, res) => {
const { data, originalRequest } = req.body;
const { response_url } = originalRequest.webhookMetadata;
await fetch(response_url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
text: `✅ Extracted: ${JSON.stringify(data.extractedData, null, 2)}`,
}),
});
res.status(200).json({ received: true });
});Scheduled Monitoring with Webhooks
Combine schedules with webhooks to build monitoring pipelines:
// Schedule configuration (via Cloud dashboard or API)
{
"name": "Competitor Price Monitor",
"schedule": "0 9 * * *",
"request": {
"tool": "extract",
"params": {
"user_input": "Extract all product prices",
"tab_urls": ["https://competitor.com/products"],
"schema": {
"fields": [
{ "name": "product", "type": "string" },
{ "name": "price", "type": "number" }
]
}
},
"webhookUrl": "https://your-server.com/price-monitor"
}
}Best Practices
- Always verify webhook signatures in production
- Respond with 200 immediately, process async
- Store a requestId mapping to correlate requests with callbacks
- Use webhookMetadata to pass through context you'll need in the callback
- Implement idempotency—you may receive the same webhook twice on retries
- Set up alerting for failed webhook deliveries
- Use HTTPS endpoints only (HTTP will be rejected)