Scrapers built on Intuned typically run via Jobs on a schedule. When a JobRun completes, you want that data sent somewhere for processing.Webhook integration delivers scraped data to your endpoint in real-time as each API Run completes. This enables instant processing without polling—your backend receives results the moment they’re ready.
While this guide focuses on scraping, webhook integration works for any Intuned Job—webhooks deliver Run results from any automation.
Add an endpoint to your backend that accepts POST requests from Intuned. The endpoint should parse JSON payloads and return a 200 status quickly to avoid timeouts.
// Types for the webhook payloadinterface WebhookPayload { workspaceId: string; project: { id: string; name: string }; projectJob: { id: string }; projectJobRun: { id: string }; apiInfo: { name: string; parameters: any; runId: string; result: { status: 'completed' | 'failed'; result?: any; error?: string; message?: string; }; };}// Add this endpoint to your Express appapp.post('/webhooks/intuned', async (req, res) => { const payload = req.body as WebhookPayload; console.log('Received webhook from Intuned'); console.log(`Project: ${payload.project.name}`); console.log(`JobRun: ${payload.projectJobRun.id}`); console.log(`Status: ${payload.apiInfo.result.status}`); // Acknowledge receipt immediately res.status(200).json({ received: true }); // Process the data asynchronously processWebhookData(payload).catch(err => { console.error('Error processing webhook:', err); });});async function processWebhookData(payload: WebhookPayload) { if (payload.apiInfo.result.status === 'completed') { console.log('Data:', payload.apiInfo.result.result); // Add your processing logic here } else { console.error('Failed:', payload.apiInfo.result.message); }}
Copy
Ask AI
from flask import request, jsonifyfrom typing import Dict, Any# Add this route to your Flask app@app.route('/webhooks/intuned', methods=['POST'])def intuned_webhook(): payload: Dict[str, Any] = request.get_json() app.logger.info('Received webhook from Intuned') app.logger.info(f"Project: {payload['project']['name']}") app.logger.info(f"JobRun: {payload['projectJobRun']['id']}") app.logger.info(f"Status: {payload['apiInfo']['result']['status']}") # Acknowledge receipt immediately response = jsonify({'received': True}) # Process the data asynchronously try: process_webhook_data(payload) except Exception as e: app.logger.error(f'Error processing webhook: {e}') return response, 200def process_webhook_data(payload: Dict[str, Any]): result = payload['apiInfo']['result'] if result['status'] == 'completed': app.logger.info(f"Data: {result.get('result')}") # Add your processing logic here else: app.logger.error(f"Failed: {result.get('message')}")
Need a test server? Create one from scratch
If you don’t have an existing backend and want to test webhooks, create a minimal server:
Express (Node.js)
Flask (Python)
Copy
Ask AI
# Create a new directorymkdir intuned-webhook-server && cd intuned-webhook-server# Initialize project and install dependenciesnpm init -y && npm pkg set type="module"npm install expressnpm install -D typescript @types/node @types/express# Initialize TypeScriptnpx tsc --init
Create server.ts:
Copy
Ask AI
import express from 'express';const app = express();app.use(express.json());// Add the webhook endpoint code from above hereconst PORT = process.env.PORT || 3000;app.listen(PORT, () => { console.log(`Webhook server running on port ${PORT}`);});
Run the server:
Copy
Ask AI
npx tsx server.ts
Copy
Ask AI
# Create a new directorymkdir intuned-webhook-server && cd intuned-webhook-server# Initialize project and install Flaskuv inituv add flask
Create main.py:
Copy
Ask AI
from flask import Flask, request, jsonifyfrom typing import Dict, Anyimport loggingapp = Flask(__name__)logging.basicConfig(level=logging.INFO)# Add the webhook endpoint code from above hereif __name__ == '__main__': app.run(host='0.0.0.0', port=3000)
You can use an existing project or create a new one.For this example, we’ll use the ecommerce-scraper-quickstart project that you can deploy using the Deploy your first scraper quickstart tutorial.
Always use HTTPS endpoints in production to ensure data security. Store authentication tokens in environment variables rather than hardcoding them.
Trigger the Job
Dashboard
TypeScript SDK
Python SDK
In the Jobs tab, find the Job you created
Select … next to the Job
Select Trigger
The Job starts running immediately. You’ll see the JobRun appear in the dashboard with status updates.
Copy
Ask AI
import { IntunedClient } from "@intuned/client";const intunedClient = new IntunedClient({ workspaceId: "your-workspace-id", apiKey: process.env["INTUNED_API_KEY"] ?? "",});async function triggerJob() { const result = await intunedClient.project.jobs.trigger( "ecommerce-scraper-quickstart", "default-with-webhook" ); console.log(`JobRun started: ${result.id}`); console.log(`Monitor at: https://app.intuned.io/workspaces/your-workspace-id/projects/ecommerce-scraper-quickstart/jobs/default-with-webhook/${result.id}`);}triggerJob();
Copy
Ask AI
from intuned_client import IntunedClientimport oswith IntunedClient( workspace_id="your-workspace-id", api_key=os.getenv("INTUNED_API_KEY", ""),) as ic_client: result = ic_client.project.jobs.trigger( project_name="ecommerce-scraper-quickstart", job_id="default-with-webhook" ) print(f"JobRun started: {result.id}") print(f"Monitor at: https://app.intuned.io/workspaces/your-workspace-id/projects/ecommerce-scraper-quickstart/jobs/default-with-webhook/{result.id}")
After triggering the Job:
Job starts immediately - Visible in Intuned dashboard
API Runs execute - The list API runs first, then details APIs for each product
Webhooks deliver - When each API Run completes, Intuned sends a webhook to your endpoint
The ecommerce scraper uses extendPayload to create detail tasks for each discovered product. You’ll receive multiple webhooks: one for the initial list Run, then one for each details Run as they complete.
If webhook delivery fails, Intuned retries with exponential backoff—up to 5 attempts total. Unlike S3/R2 sinks, webhook failures don’t pause the Job; the Job continues and undelivered webhooks are skipped after all retries are exhausted.
Check your webhook endpoint logs - you should see payloads with this structure:
After receiving webhook data, you’ll typically want to process it. Here are common patterns:
Store in a database: Parse the extracted data and upsert records. Use the runId as a unique key to handle duplicates from webhook retries.
Validate and clean: Check data structure and clean values before processing. Filter out malformed records and log validation errors for debugging.
Send to monitoring: Route failures to error tracking services (Sentry, Datadog) for debugging. Track metrics like success rates and processing times.
Trigger downstream workflows: Use webhook data to kick off additional processes—update other services, publish to message queues, or send notifications based on the results.
Cause: Endpoint URL is incorrect, not publicly accessible, or blocked by firewall/SSL issues.Solution: Test endpoint accessibility with curl from an external server. Check server logs for incoming requests and verify the webhook URL in your Intuned Job configuration.
Cause: Your endpoint takes too long to respond (>5 seconds).Solution: Return 200 status immediately, then process data asynchronously using background jobs or queues.
Cause: Authorization header mismatch between Job configuration and endpoint validation.Solution: Verify the header name and value in your Job configuration. Check for extra spaces or encoding issues in the secret token.
Cause: Intuned retries on slow responses or network issues.Solution: Implement idempotency using the runId field from the payload as a deduplication key.