Webhook-triggered tasks and API runs need a paid plan and use credits for the resources they consume, and so does running rtrvr's agent in your Chrome through API or MCP. Receiving a completion callback does not make an API run part of Free Mode. See plan and usage details.
Webhooks let external systems trigger rtrvr workflows via HTTP POST and let rtrvr send completion/failure callbacks back to your infrastructure. Use this for Zapier, Make, n8n, backend jobs, and server-to-server pipelines.
Platform Compatibility
| Platform | HTTP Timeout | Compatibility | Recommended Approach |
|---|---|---|---|
| n8n | 100s (Cloud) | Excellent | Direct calls work for most tasks |
| Make.com | Up to 300s | Excellent | Set timeout to 120s+ |
| Zapier | 30s fixed | Use callbacks | Provide webhookUrl for async results |
Inbound Webhooks (Zapier, Make, n8n → rtrvr)
Any service that can send an HTTP POST can trigger rtrvr.ai workflows. Use the MCP endpoint to control your logged-in browser, or the /agent endpoint for cloud browser execution.
For a fixed task, the Cloud dashboard's Triggers panel (Cloud → Triggers) mints a per-trigger URL (POST https://mcp.rtrvr.ai/v1/webhooks/trigger/{id}, Bearer API key) whose target is a saved tool, a past cloud run, or a prompt stored on the trigger: a prompt trigger runs that prompt on every POST and appends the body's optional input to it.
Every endpoint here, trigger URLs included, takes your API key as Authorization: Bearer rtrvr_…, as the bare key (Authorization: rtrvr_…), or in an x-api-key header. In n8n, Make or Zapier "Header Auth" credentials, set Name to Authorization and Value to your key (the Bearer prefix is optional), or set Name to x-api-key.
MCP Endpoint (Your Browser)
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
}Agent Endpoint (Cloud Browser)
POST https://api.rtrvr.ai/agent
Headers:
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
Body:
{
"input": "Extract company info and contact details",
"urls": ["https://example.com"],
"webhookUrl": "https://your-server.com/callback",
"response": { "verbosity": "final" }
}n8n Integration
n8n Cloud has a 100-second timeout: comfortably above most rtrvr task durations. Use either endpoint depending on whether you need your logged-in browser or a cloud browser.
// n8n → MCP (your logged-in browser)
{
"method": "POST",
"url": "https://mcp.rtrvr.ai",
"body": {
"tool": "planner",
"params": {
"user_input": "{{ $json.task_description }}",
"tab_urls": ["{{ $json.target_url }}"]
},
"webhookUrl": "{{ $node.Webhook.url }}"
}
}
// n8n → /agent (cloud browser)
{
"method": "POST",
"url": "https://api.rtrvr.ai/agent",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{ "name": "Authorization", "value": "Bearer {{ $credentials.rtrvrApiKey }}" },
{ "name": "Content-Type", "value": "application/json" }
]
},
"sendBody": true,
"bodyParameters": {
"parameters": [
{ "name": "input", "value": "={{ $json.taskDescription }}" },
{ "name": "urls", "value": "={{ [$json.targetUrl] }}" }
]
}
}Make (Integromat) Integration
- 01
Add an "HTTP > Make a request" module
- 02
URL: https://mcp.rtrvr.ai | Method: POST
- 03
Headers: Authorization (Bearer token), Content-Type (application/json)
- 04
Body type: Raw, Content type: JSON
- 05
Request content: Your tool + params JSON
Zapier Integration
Zapier has a fixed 30-second HTTP timeout. Use the webhook pattern: Zap 1 triggers rtrvr with a webhookUrl pointing to a Catch Hook in Zap 2, which receives and processes the results.
- 01
Add a "Webhooks by Zapier" action to your Zap
- 02
Select "POST" as the method
- 03
Set URL to: https://mcp.rtrvr.ai
- 04
Add headers: Authorization = Bearer rtrvr_your_api_key, Content-Type = application/json
- 05
Set Data to your JSON payload (tool + params)
- 06
Set webhookUrl to a Catch Hook URL in a second Zap
// Example: 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/"
}Available Tools
| Tool | Use Case | Execution |
|---|---|---|
| agent | End-to-end task from one prompt; your signed-in Chrome when connected, else a cloud browser (target overrides) | Local or cloud browser |
| scrape | Read pages (text + accessibility tree); same routing as agent | Local or cloud browser |
| planner | Complex multi-step tasks from natural language | Local or cloud browser |
| extract | Structured data extraction with optional schema | Local or cloud browser |
| act | Page interactions (click, type, navigate) | Local or cloud browser |
| crawl | Multi-page crawling with extraction | Local or cloud browser |
| replay_workflow | Re-run a previous workflow by ID or URL | Local or cloud browser |
| get_browser_tabs | List open tabs | Local browser only |
| user_<toolName> | Run one of your saved tools (subroutines, custom JavaScript tools) with its parameters; your signed-in Chrome when connected, else a cloud browser | Local or cloud browser |
Saved tools no longer need your Chrome open. A user_<toolName> call (REST tool, or the MCP tool of the same name) runs on your extension when a device is connected and falls back to a cloud browser otherwise; pass target: "cloud" to force the cloud browser or target: "device" to keep the old behaviour (a 503 when nothing is connected). Cloud runs open the tool's page with its stored website session, bill browser time and inference like an agent run (creditsUsed is in the response), and answer 202 accepted with a trajectoryId when the tool is still running after ~25 seconds: add a webhookUrl to receive the result, or read it from Cloud executions. Cloud execution needs an API key with the cloud agent capability.
Outbound Webhooks (rtrvr → Your Server)
Include a webhookUrl in any API request to receive results when the workflow completes. rtrvr.ai will POST the full response to your endpoint.
Enabling Outbound Webhooks
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 Payloads
Success Payload
{
"event": "workflow.completed",
"timestamp": "2025-01-15T12:00:00.000Z",
"requestId": "req_abc123xyz",
"success": true,
"data": {
"taskCompleted": true,
"output": { ... },
"extractedData": [ ... ],
"creditsUsed": 5
},
"metadata": {
"tool": "planner",
"deviceId": "dj75mmaTWP0",
"executionTime": 15234,
"creditsRemaining": 9995
},
"originalRequest": {
"tool": "planner",
"params": { ... }
}
}Error Payload
{
"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
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;
// Reject stale timestamps (> 5 minutes)
if (Date.now() - parseInt(timestamp) > 300000) {
return res.status(400).json({ error: 'Timestamp too old' });
}
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());
// Process asynchronously — respond 200 immediately
res.status(200).json({ received: true });
processWebhook(data);
});# Flask
import hmac, hashlib, time, os
from flask import Flask, request, jsonify
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')
if abs(time.time() * 1000 - int(timestamp)) > 300000:
return jsonify({'error': 'Timestamp too old'}), 400
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
return jsonify({'received': True}) # Process asyncRetry Behavior
Failed deliveries are retried with exponential backoff:
- 01
Attempt 1: Immediate
- 02
Attempt 2: After 5 seconds
- 03
Attempt 3: After 30 seconds
- 04
Attempt 4: After 2 minutes
- 05
Attempt 5: After 10 minutes (final)
Common Patterns
Lead Enrichment Pipeline
New lead in CRM → rtrvr extracts company data → webhook returns enriched info → update CRM record.
// Trigger: New HubSpot contact
// Action: POST to https://api.rtrvr.ai/agent
{
"input": "Visit this company website and extract: company size, industry, tech stack, and key contacts",
"urls": ["{{contact.company_website}}"],
"webhookUrl": "https://hooks.zapier.com/catch/123/enrich/",
"response": { "verbosity": "final" }
}
// Webhook receives enriched data → Update CRMBrowser Trigger → Sheet Log (Zero Server)
The simplest pattern: monitor a site for notifications and log events to Google Sheets. No server, no API, no webhook endpoint needed.
- 01
Create a workflow that extracts data from the site and appends to a Google Sheet
- 02
Set up a Browser Trigger on that site with appropriate filters
- 03
Configure sheet output to "Append to same sheet on each run"
- 04
Every matching notification adds a row: building a running log automatically
Scheduled Price Monitoring
Cron schedule → rtrvr checks competitor prices → compare with previous data → alert if changed.
// Schedule: Daily at 9am via n8n Cron node
// Action: POST to https://api.rtrvr.ai/scrape
{
"urls": [
"https://competitor1.com/pricing",
"https://competitor2.com/pricing"
]
}
// Compare extracted prices with yesterday's data
// If changed → Send Slack/email notificationBrowser Trigger + Outbound Webhook (Hybrid)
Flow:
1. Browser Trigger monitors slack.com for "deployment failed" notifications
2. Trigger fires → workflow extracts error details from the Slack thread
3. Workflow calls your server via rtrvr.ai API with webhookUrl set
4. Server receives error details → creates a Jira ticket automatically
Result: Slack notification → browser extraction → server-side ticket creation
No Slack API required: the browser does the heavy liftingAuthenticated Data Sync (MCP)
Your app triggers → rtrvr uses your logged-in browser via MCP → data synced to your database.
// Trigger: Webhook from your application
// Action: POST to https://mcp.rtrvr.ai
{
"tool": "extract",
"params": {
"user_input": "Export my order history from the last 30 days",
"tab_urls": ["https://vendor-portal.com/orders"]
},
"webhookUrl": "https://your-app.com/api/orders/sync"
}
// Your browser navigates using your login session
// Results sent to webhook → stored in databaseZapier → rtrvr → Zapier (Round-trip)
- 01
Zap 1: New Google Form submission → POST to mcp.rtrvr.ai (include webhookUrl pointing to Zap 2)
- 02
Zap 2: Catch Hook receives results → Add row to Google Sheets
Slack Command → rtrvr → Slack Message
app.post('/slack/commands', async (req, res) => {
const { text, response_url } = req.body;
res.status(200).json({ text: '🔄 Running extraction...' }); // Ack < 3s
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 },
}),
});
});
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 });
});Choosing the Right Approach
| Approach | Trigger Signal | Best For | Availability |
|---|---|---|---|
| Browser Triggers | Push notification in a tab | Sites without APIs: social media, chat, SaaS dashboards | While Chrome is open |
| Schedules | Cron / interval timer | Periodic collection, monitoring, recurring reports | Chrome or 24/7 cloud |
| Inbound Webhooks | HTTP POST from external service | Zapier / Make / n8n, CI/CD, server-to-server | 24/7 cloud |
Best Practices
- 01
Verify signatures on every callback in production
- 02
Implement idempotency to handle retries safely
- 03
Use HTTPS-only endpoints
- 04
Pass webhookMetadata for correlation IDs and routing context
