Synthetic monitoring is a proactive approach to monitoring web applications by simulating user interactions before real users encounter issues. Instead of waiting for problems to occur, synthetic monitoring continuously tests your application from multiple locations worldwide.
In this guide, we'll explore what synthetic monitoring is, how it works, and why it's essential for modern web applications.
What is Synthetic Monitoring?#
Synthetic monitoring (also called active monitoring or proactive monitoring) is the practice of using automated scripts to simulate user interactions with your web application. These scripts run on a schedule from various geographic locations, testing critical user flows and reporting on availability, performance, and functionality.
How Synthetic Monitoring Works#
- Script Creation: Define user journeys (e.g., login, checkout, search)
- Scheduled Execution: Run scripts every 1-15 minutes from multiple locations
- Data Collection: Record response times, errors, and screenshots
- Alerting: Notify teams when issues are detected
- Analysis: Track trends and identify performance degradation
Example Synthetic Test#
// Synthetic test for e-commerce checkouttest("checkout flow", async ({ page }) => {// 1. Navigate to product pageawait page.goto("https://example.com/products/123");// 2. Add to cartawait page.getByTestId("add-to-cart").click();// 3. Proceed to checkoutawait page.getByTestId("checkout").click();// 4. Fill shipping info (use env vars for secrets)await page.getByTestId("email").fill(process.env.TEST_EMAIL ?? "");await page.getByTestId("address").fill("123 Main St");// 5. Complete purchaseawait page.getByTestId("place-order").click();// 6. Verify successawait expect(page.getByTestId("success-message")).toBeVisible();});
// Synthetic test for e-commerce checkouttest("checkout flow", async ({ page }) => {// 1. Navigate to product pageawait page.goto("https://example.com/products/123");// 2. Add to cartawait page.getByTestId("add-to-cart").click();// 3. Proceed to checkoutawait page.getByTestId("checkout").click();// 4. Fill shipping info (use env vars for secrets)await page.getByTestId("email").fill(process.env.TEST_EMAIL ?? "");await page.getByTestId("address").fill("123 Main St");// 5. Complete purchaseawait page.getByTestId("place-order").click();// 6. Verify successawait expect(page.getByTestId("success-message")).toBeVisible();});
This test runs every 5 minutes from multiple locations, alerting you immediately if any step fails.
Synthetic vs Real User Monitoring (RUM)#
Both synthetic monitoring and Real User Monitoring (RUM) are essential, but they serve different purposes:
| Aspect | Synthetic Monitoring | Real User Monitoring (RUM) |
|---|---|---|
| Data Source | Simulated users | Actual users |
| Coverage | Predefined scenarios | All user interactions |
| Timing | Continuous (24/7) | Only when users visit |
| Geography | Controlled locations | Wherever users are |
| Detection | Proactive (before users) | Reactive (after users affected) |
| Cost | Predictable | Scales with traffic |
| Use Case | Catch issues early | Understand real user experience |
When to Use Each#
Use Synthetic Monitoring for:
- Monitoring critical user flows (login, checkout, registration)
- Testing from specific geographic regions
- Monitoring during low-traffic periods (nights, weekends)
- Service-level agreement (SLA) compliance and uptime tracking
- Third-party API monitoring
Use Real User Monitoring for:
- Understanding actual user experience
- Identifying performance issues in production
- Tracking user behavior patterns
- Measuring Core Web Vitals
- A/B testing performance impact
Best Practice: Use both together for comprehensive monitoring.
Benefits of Synthetic Monitoring#
1. Proactive Issue Detection#
Catch problems before users do: when a synthetic check fails, you get an alert. You might fix it before anyone notices, or at least you’re investigating before the first support ticket.
2. Global Performance Visibility#
Test from multiple locations to ensure consistent performance:
{"locations": ["us-east-1", // New York"eu-west-1", // Ireland"ap-southeast-1", // Singapore"sa-east-1" // São Paulo]}
{"locations": ["us-east-1", // New York"eu-west-1", // Ireland"ap-southeast-1", // Singapore"sa-east-1" // São Paulo]}
Insight: Discover that your API is slow in Asia due to lack of content delivery network (CDN) coverage.
3. SLA compliance#
Track uptime and performance against SLAs:
Monthly Uptime: 99.95%Average Response Time: 287msSLA Target: 99.9% uptime, <500ms responseStatus: ✅ Meeting SLA
Monthly Uptime: 99.95%Average Response Time: 287msSLA Target: 99.9% uptime, <500ms responseStatus: ✅ Meeting SLA
4. Baseline Performance Metrics#
Establish performance baselines to detect degradation:
Week 1: Average load time 1.2sWeek 2: Average load time 1.8s ⚠️Alert: 50% performance degradation detected
Week 1: Average load time 1.2sWeek 2: Average load time 1.8s ⚠️Alert: 50% performance degradation detected
5. Third-Party Dependency Monitoring#
Monitor external services you depend on:
// Monitor Stripe APItest("payment processing", async ({ page }) => {const response = await page.request.post("https://api.stripe.com/v1/charges",{data: {/* test charge */},},);expect(response.status()).toBe(200);});
// Monitor Stripe APItest("payment processing", async ({ page }) => {const response = await page.request.post("https://api.stripe.com/v1/charges",{data: {/* test charge */},},);expect(response.status()).toBe(200);});
Types of Synthetic Monitoring#
1. Uptime Monitoring#
Simple HTTP (Hypertext Transfer Protocol) checks to verify availability:
{"url": "https://example.com","method": "GET","expected_status": 200,"interval": "1m"}
{"url": "https://example.com","method": "GET","expected_status": 200,"interval": "1m"}
Use case: Ensure your homepage is accessible.
2. API Monitoring#
Test API endpoints with assertions:
{"url": "https://api.example.com/users","method": "GET","headers": { "Authorization": "Bearer token" },"assertions": [{ "type": "status", "value": 200 },{ "type": "response_time", "value": "max_500ms" },{ "type": "json_path", "path": "$.users[0].id", "exists": true }]}
{"url": "https://api.example.com/users","method": "GET","headers": { "Authorization": "Bearer token" },"assertions": [{ "type": "status", "value": 200 },{ "type": "response_time", "value": "max_500ms" },{ "type": "json_path", "path": "$.users[0].id", "exists": true }]}
Use case: Verify API functionality and performance.
3. Browser-Based Monitoring#
Full user flow testing with real browsers:
test("user registration", async ({ page }) => {await page.goto("/signup");await page.getByTestId("email").fill(process.env.TEST_EMAIL ?? "");await page.getByTestId("password").fill(process.env.TEST_PASSWORD ?? "");await page.getByTestId("submit").click();await expect(page).toHaveURL("/dashboard");});
test("user registration", async ({ page }) => {await page.goto("/signup");await page.getByTestId("email").fill(process.env.TEST_EMAIL ?? "");await page.getByTestId("password").fill(process.env.TEST_PASSWORD ?? "");await page.getByTestId("submit").click();await expect(page).toHaveURL("/dashboard");});
Use case: Test complex JavaScript-heavy applications.
4. Transaction Monitoring#
Multi-step business-critical flows:
test("complete purchase", async ({ page }) => {// Step 1: Loginawait login(page);// Step 2: Add product to cartawait addToCart(page, "product-123");// Step 3: Checkoutawait checkout(page);// Step 4: Paymentawait processPayment(page);// Step 5: Verify order confirmationawait verifyOrderConfirmation(page);});
test("complete purchase", async ({ page }) => {// Step 1: Loginawait login(page);// Step 2: Add product to cartawait addToCart(page, "product-123");// Step 3: Checkoutawait checkout(page);// Step 4: Paymentawait processPayment(page);// Step 5: Verify order confirmationawait verifyOrderConfirmation(page);});
Use case: Ensure end-to-end business processes work.
How to Implement Synthetic Monitoring#
Step 1: Identify Critical User Journeys#
List the most important flows in your application:
E-commerce:
- Homepage → Product → Add to Cart → Checkout → Purchase
- User Registration
- Login
- Search
SaaS Application:
- Sign Up → Onboarding → First Action
- Login → Dashboard Load
- Create New Project
- Invite Team Member
Step 2: Choose a Monitoring Tool#
For Simple Uptime Checks:
- ObserveOne (free tier and paid tiers)
- UptimeRobot (free)
- Pingdom
- StatusCake
For API Monitoring:
- ObserveOne
- Postman Monitors
- Checkly
For Browser-Based Monitoring:
- ObserveOne (Autopilot)
- Checkly (Playwright-based, AI-assisted debugging)
- Datadog Synthetics
Step 3: Write Your First Synthetic Test#
Example with ObserveOne:
// Autopilot input: paste a URL and an optional goal{"url": "https://example.com","goal": "Cover the main user flows like pricing, signup, and login"}// Autopilot generates editable Playwright like this, and a healer agent re-fixes it when the app changestest('login flow', async ({ page }) => {await page.goto('https://example.com/login');await page.getByTestId('email').fill(process.env.TEST_EMAIL ?? '');await page.getByTestId('password').fill(process.env.TEST_PASSWORD ?? '');await page.getByTestId('submit').click();await expect(page.getByTestId('dashboard')).toBeVisible();});
// Autopilot input: paste a URL and an optional goal{"url": "https://example.com","goal": "Cover the main user flows like pricing, signup, and login"}// Autopilot generates editable Playwright like this, and a healer agent re-fixes it when the app changestest('login flow', async ({ page }) => {await page.goto('https://example.com/login');await page.getByTestId('email').fill(process.env.TEST_EMAIL ?? '');await page.getByTestId('password').fill(process.env.TEST_PASSWORD ?? '');await page.getByTestId('submit').click();await expect(page.getByTestId('dashboard')).toBeVisible();});
Step 4: Configure Monitoring Locations#
Test from regions where your users are:
{"locations": ["us-east", // 40% of users"eu-west", // 35% of users"ap-south" // 25% of users]}
{"locations": ["us-east", // 40% of users"eu-west", // 35% of users"ap-south" // 25% of users]}
Step 5: Set Up Alerts#
Configure alerts for different severity levels:
{"alerts": [{"condition": "failure","channels": ["slack", "pagerduty"],"severity": "critical"},{"condition": "slow_response > 2s","channels": ["email"],"severity": "warning"}]}
{"alerts": [{"condition": "failure","channels": ["slack", "pagerduty"],"severity": "critical"},{"condition": "slow_response > 2s","channels": ["email"],"severity": "warning"}]}
Step 6: Create Dashboards#
Visualize synthetic monitoring data:
- Uptime %: 99.95% this month
- Average Response Time: 287ms
- Failed Checks: 3 (all resolved)
- Slowest Location: Asia-Pacific (450ms avg)
Best Practices#
1. Test from Multiple Locations#
Don't rely on a single monitoring location:
// Bad: Only monitoring from one location{ "location": "us-east" }// Good: Monitoring from user regions{ "locations": ["us-east", "eu-west", "ap-southeast"] }
// Bad: Only monitoring from one location{ "location": "us-east" }// Good: Monitoring from user regions{ "locations": ["us-east", "eu-west", "ap-southeast"] }
2. Use Realistic Test Data#
Avoid using obvious test data that might be filtered:
// Bad// Good{ "email": "monitor.user.${timestamp}@example.com" }
// Bad// Good{ "email": "monitor.user.${timestamp}@example.com" }
3. Set Appropriate Check Intervals#
Balance cost and detection speed:
- Critical flows: Every 1-5 minutes
- Important flows: Every 10-15 minutes
- Non-critical: Every 30-60 minutes
4. Monitor Third-Party Dependencies#
Test external services separately:
// Monitor Stripe separately from your checkout flowtest("stripe api health", async ({ page }) => {const response = await page.request.get("https://api.stripe.com/v1/charges");expect(response.status()).toBe(401); // Expect auth error (proves API is up)});
// Monitor Stripe separately from your checkout flowtest("stripe api health", async ({ page }) => {const response = await page.request.get("https://api.stripe.com/v1/charges");expect(response.status()).toBe(401); // Expect auth error (proves API is up)});
5. Use Assertions, Not Just Status Codes#
Verify actual functionality:
// Bad: Only checking statusexpect(response.status()).toBe(200);// Good: Checking contentexpect(response.status()).toBe(200);expect(await response.json()).toHaveProperty("users");expect((await response.json()).users.length).toBeGreaterThan(0);
// Bad: Only checking statusexpect(response.status()).toBe(200);// Good: Checking contentexpect(response.status()).toBe(200);expect(await response.json()).toHaveProperty("users");expect((await response.json()).users.length).toBeGreaterThan(0);
6. Implement Retry Logic#
Avoid false positives from transient issues:
{"retries": 2,"retry_interval": "30s"}
{"retries": 2,"retry_interval": "30s"}
7. Track Trends, Not Just Failures#
Monitor performance degradation over time:
Week 1: 1.2s averageWeek 2: 1.4s average ⚠️ Trend alertWeek 3: 1.8s average 🚨 Critical
Week 1: 1.2s averageWeek 2: 1.4s average ⚠️ Trend alertWeek 3: 1.8s average 🚨 Critical
Common Pitfalls#
1. Over-Monitoring#
Problem: Running checks too frequently wastes resources.
Solution: Match check frequency to criticality:
- Login page: Every 5 minutes
- Blog posts: Every 30 minutes
2. Ignoring Geographic Distribution#
Problem: Only monitoring from one region.
Solution: Test from all regions where you have users.
3. Not Testing Authentication#
Problem: Only checking public pages.
Solution: Test authenticated flows:
test("authenticated dashboard", async ({ page }) => {await login(page);await expect(page.getByTestId("user-dashboard")).toBeVisible();});
test("authenticated dashboard", async ({ page }) => {await login(page);await expect(page.getByTestId("user-dashboard")).toBeVisible();});
4. Alert Fatigue#
Problem: Too many alerts for minor issues.
Solution: Use alert thresholds:
{"alert_after_failures": 2, // Alert after 2 consecutive failures"alert_after_slow_responses": 3 // Alert after 3 slow responses}
{"alert_after_failures": 2, // Alert after 2 consecutive failures"alert_after_slow_responses": 3 // Alert after 3 slow responses}
Synthetic Monitoring Tools Comparison#
| Tool | Browser Testing | API Testing | Multi-Location | AI Features | Starting Price |
|---|---|---|---|---|---|
| ObserveOne | Yes (AI-powered) | Yes | Yes | Yes | $24/month |
| Checkly | Yes (Playwright-based) | Yes | Yes | Yes (AI-assisted) | $7/month |
| Datadog | Yes | Yes | Yes | Limited | $15/test/month |
| Pingdom | Yes | Limited | Yes | No | $10/month |
| UptimeRobot | No | Yes | Limited | No | Free |
Conclusion#
Synthetic monitoring helps you catch issues before users do by testing critical flows from multiple locations. Start with your most important user journey and expand from there.
Use ObserveOne's free tier to generate self-healing Playwright suites for your critical flows with Autopilot in minutes.