Developer workflow

WebChange Quickstart

Create a watch, receive a signed webhook when meaningful content changes, and inspect history and usage. Every command below uses the implemented API.

Before you start

Keep the API key in an environment variable rather than in scripts, shell history, source control, URLs, or browser code. The key is shown once when it is issued; if it leaks, contact support to revoke it and issue a new one. Never send the full API key to support.

Set BASE_URL to the WebChange API host. The value below is the intended production hostname; confirm availability for your environment before relying on it.

bash
export BASE_URL="https://webchange.exuere.com"
read -rs WEBCHANGE_API_KEY && export WEBCHANGE_API_KEY

Illustrative page and CSS selector

The watch and webhook URLs below are placeholders for the documented request shape. They are not the WebChange API hostname (https://webchange.exuere.com). WebChange does not host a live demonstration page or webhook receiver. Substitute your own HTTPS page URL and your own HTTPS webhook endpoint before running the commands.

This illustrative HTML shows how a CSS selector narrows monitoring. In the example, #pricing .price selects the price paragraph and monitors only its visible text ($12 / month). Edits outside that element—such as the footer—do not count as a change. Replace the placeholder page URL (https://example.com/pricing) with your own HTTPS page before running commands; it is not the WebChange API host (https://webchange.exuere.com).

html
<html>
  <body>
    <section id="pricing">
      <h2>Plans</h2>
      <p class="price">$12 / month</p>
    </section>
    <footer>Last updated: Monday</footer>
  </body>
</html>

The selector picks the first matching element. Whitespace is normalized and script, style, noscript, and template content is ignored. The page is fetched as served HTML; JavaScript is not executed, so content rendered only in the browser cannot be selected. Without a selector, the whole page body is compared.

check_interval_seconds sets the scheduled check cadence in seconds. For the documented paid plan, the minimum is 900 (15 minutes). Longer intervals are supported—for example 1800 (30 minutes) and 3600 (1 hour). Manual and scheduled checks share one monthly allowance, so running every watch at the minimum interval can exhaust that allowance before the month ends.

Walkthrough

1. How to verify your API key

Every request authenticates with the X-API-Key header. A missing header returns 401 authentication_required; a wrong, revoked, or expired key returns 401 invalid_api_key.

bash
curl -sS -X GET "$BASE_URL/v1/auth/me" \
  -H "X-API-Key: $WEBCHANGE_API_KEY"

Expected status: 200

json
{
  "email": "[email protected]",
  "plan": "paid"
}

The response confirms which account and plan the key belongs to.

2. How to create a watch

Choose one of the options below. Create a single watch, copy its id into WATCH_ID, and use that watch for the rest of this walkthrough—you do not need to run both options. Replace the placeholder page URL (https://example.com/pricing) with your own HTTPS page. The selector field is optional. check_interval_seconds sets the scheduled check cadence in seconds. For the documented paid plan, the minimum is 900 (15 minutes). Longer intervals are supported—for example 1800 (30 minutes) and 3600 (1 hour). Manual and scheduled checks share one monthly allowance, so running every watch at the minimum interval can exhaust that allowance before the month ends.

Option A: Monitor the entire page

Omit selector to compare the full page body after normalization.

bash
curl -sS -X POST "$BASE_URL/v1/watches" \
  -H "X-API-Key: $WEBCHANGE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "Example pricing", "url": "https://example.com/pricing", "check_interval_seconds": 900}'

Expected status: 201

json
{
  "id": "3f1c2b8e-6d4a-4f0e-9a51-2c7d8e9f0a1b",
  "name": "Example pricing",
  "url": "https://example.com/pricing",
  "selector": null,
  "is_enabled": true,
  "check_interval_seconds": 900,
  "next_check_at": "2026-11-02T12:15:00Z",
  "last_checked_at": null,
  "created_at": "2026-11-02T12:00:00Z",
  "updated_at": "2026-11-02T12:00:00Z"
}

Option B: Monitor a specific element

Use this option for the remaining walkthrough steps. #pricing .price selects the price text in the illustrative HTML below.

bash
curl -sS -X POST "$BASE_URL/v1/watches" \
  -H "X-API-Key: $WEBCHANGE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "Example pricing", "url": "https://example.com/pricing", "selector": "#pricing .price", "check_interval_seconds": 900}'

Expected status: 201

json
{
  "id": "3f1c2b8e-6d4a-4f0e-9a51-2c7d8e9f0a1b",
  "name": "Example pricing",
  "url": "https://example.com/pricing",
  "selector": "#pricing .price",
  "is_enabled": true,
  "check_interval_seconds": 900,
  "next_check_at": "2026-11-02T12:15:00Z",
  "last_checked_at": null,
  "created_at": "2026-11-02T12:00:00Z",
  "updated_at": "2026-11-02T12:00:00Z"
}

Creating a watch does not fetch the page. Copy the returned id: export WATCH_ID="<id>".

3. How to configure a webhook

Replace the placeholder webhook URL with your own HTTPS receiver. Destinations must use HTTPS; deliveries to private or internal addresses are refused. Each PUT creates or replaces the configuration and rotates the signing secret.

bash
curl -sS -X PUT "$BASE_URL/v1/watches/$WATCH_ID/webhook" \
  -H "X-API-Key: $WEBCHANGE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://hooks.example.com/webchange"}'

Expected status: 200

json
{
  "url": "https://hooks.example.com/webchange",
  "is_active": true,
  "signing_secret": "<signing secret, shown once>"
}

The signing secret is shown once, in this response only. Store it in your receiver's secret store; GET on the same path never returns it.

4. How to run your first check

A manual check fetches the page now and consumes one check from your monthly allowance.

bash
curl -sS -X POST "$BASE_URL/v1/watches/$WATCH_ID/check" \
  -H "X-API-Key: $WEBCHANGE_API_KEY"

Expected status: 200

json
{
  "execution_id": "5d6e7f80-91a2-4b3c-8d4e-5f6a7b8c9d0e",
  "status": "succeeded",
  "outcome": "baseline",
  "change_event_id": null,
  "http_status_code": 200,
  "checked_at": "2026-11-02T12:00:00Z"
}

The first successful check stores a baseline. A baseline does not create a change event and does not send a webhook.

5. How to detect a webpage change

After the baseline, change the monitored content on your own webpage, then run another check. Later checks compare the selected text with the previous snapshot. A second check with no content change returns unchanged. In this documented example the price changed from $12 to $15.

bash
curl -sS -X POST "$BASE_URL/v1/watches/$WATCH_ID/check" \
  -H "X-API-Key: $WEBCHANGE_API_KEY"

Expected status: 200

json
{
  "execution_id": "6e7f8091-a2b3-4c4d-9e5f-6a7b8c9d0e1f",
  "status": "succeeded",
  "outcome": "changed",
  "change_event_id": "8b0e4d2a-1c3f-4e5a-9b6c-7d8e9f0a1b2c",
  "http_status_code": 200,
  "checked_at": "2026-11-02T12:20:00Z"
}

outcome is changed when the selected text differs; the background worker then delivers a signed watch.changed webhook, retrying with backoff on failure. If the selected text is identical the outcome is unchanged, change_event_id is null, and no webhook is sent.

6. How to view detected changes

Change history is returned newest first; limit accepts 1 to 100.

bash
curl -sS -X GET "$BASE_URL/v1/watches/$WATCH_ID/changes?limit=10" \
  -H "X-API-Key: $WEBCHANGE_API_KEY"

Expected status: 200

json
[
  {
    "id": "8b0e4d2a-1c3f-4e5a-9b6c-7d8e9f0a1b2c",
    "previous_hash": "<sha256 of previous text>",
    "current_hash": "<sha256 of current text>",
    "diff": "--- previous\n+++ current\n@@ -1 +1 @@\n-$12 / month\n+$15 / month\n",
    "created_at": "2026-11-02T12:20:00Z"
  }
]

The diff is a unified diff of the normalized selected text. A watch with no changes returns an empty array.

7. How to check your usage

Manual and scheduled checks share one monthly allowance. A check that reaches the target consumes usage even if it fails.

bash
curl -sS -X GET "$BASE_URL/v1/usage" \
  -H "X-API-Key: $WEBCHANGE_API_KEY"

Expected status: 200

json
{
  "period": "2026-11",
  "checks_used": 2,
  "checks_limit": 10000,
  "checks_remaining": 9998,
  "active_watches": 1,
  "max_watches": 25
}

When checks_remaining reaches 0, checks return 429 until next month.

8. How to list your watches

Only watches owned by your account are returned.

bash
curl -sS -X GET "$BASE_URL/v1/watches" \
  -H "X-API-Key: $WEBCHANGE_API_KEY"

Expected status: 200

json
[
  {
    "id": "3f1c2b8e-6d4a-4f0e-9a51-2c7d8e9f0a1b",
    "name": "Example pricing",
    "url": "https://example.com/pricing",
    "selector": "#pricing .price",
    "is_enabled": true,
    "check_interval_seconds": 900,
    "next_check_at": "2026-11-02T12:15:00Z",
    "last_checked_at": null,
    "created_at": "2026-11-02T12:00:00Z",
    "updated_at": "2026-11-02T12:00:00Z"
  }
]

next_check_at shows when scheduled monitoring will next run the watch.

9. How to update an existing watch

PATCH /v1/watches/$WATCH_ID modifies an existing watch without recreating it. Supported fields are name, url, selector, check_interval_seconds, and is_enabled. This example lengthens the scheduled interval from 900 seconds (15 minutes) to 3600 seconds (1 hour).

bash
curl -sS -X PATCH "$BASE_URL/v1/watches/$WATCH_ID" \
  -H "X-API-Key: $WEBCHANGE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"check_interval_seconds": 3600}'

Expected status: 200

json
{
  "id": "3f1c2b8e-6d4a-4f0e-9a51-2c7d8e9f0a1b",
  "name": "Example pricing",
  "url": "https://example.com/pricing",
  "selector": "#pricing .price",
  "is_enabled": true,
  "check_interval_seconds": 3600,
  "next_check_at": "2026-11-02T12:15:00Z",
  "last_checked_at": null,
  "created_at": "2026-11-02T12:00:00Z",
  "updated_at": "2026-11-02T12:20:00Z"
}

Provide at least one field. An empty PATCH body is rejected. Changing check_interval_seconds does not immediately recalculate the existing next_check_at; the new interval applies after the next scheduled check.

10. How to disable a watch

DELETE stops monitoring and frees an active-watch slot.

bash
curl -sS -X DELETE "$BASE_URL/v1/watches/$WATCH_ID" \
  -H "X-API-Key: $WEBCHANGE_API_KEY"

Expected status: 204

DELETE is a logical disable: the watch and its change history remain readable, and further checks return 409 watch_disabled. Re-enable with PATCH and {"is_enabled": true}.

Baseline, unchanged, changed, and no match

Manual and scheduled checks

Scheduled monitoring runs each enabled watch when its next_check_at is due, then schedules the next run from the watch's check_interval_seconds. Manual checks run immediately but do not advance next_check_at. Both kinds consume the same monthly allowance.

Verify webhook signatures

Each delivery is a JSON POST with these headers:

Compute the HMAC over the raw request body bytes before parsing JSON, compare in constant time, and reject old timestamps. The five-minute tolerance below is a receiver choice, not a WebChange setting. Return a 2xx status quickly; other responses and timeouts are retried with backoff for a limited number of attempts.

python
import hashlib
import hmac
import time

TOLERANCE_SECONDS = 300


def verify_webchange_signature(secret, timestamp, raw_body, signature):
    """secret: str, timestamp: WebChange-Timestamp header, raw_body: bytes."""
    try:
        sent_at = int(timestamp)
    except (TypeError, ValueError):
        return False
    if abs(time.time() - sent_at) > TOLERANCE_SECONDS:
        return False
    signed = timestamp.encode("ascii") + b"." + raw_body
    digest = hmac.new(secret.encode("utf-8"), signed, hashlib.sha256).hexdigest()
    return hmac.compare_digest("v1=" + digest, signature)

Common errors

Errors use the envelope {"error": {"code": "...", "message": "..."}}. Malformed JSON or unknown request fields return 422 with a detail array instead.

StatusCodeMeaning
401authentication_requiredThe X-API-Key header is missing.
401invalid_api_keyThe key is wrong, revoked, or expired.
403product_access_deniedThe account has no active WebChange access.
403insufficient_scopeThe key's scopes do not allow this operation.
409plan_limit_exceededAll active-watch slots are in use.
422invalid_check_intervalcheck_interval_seconds is below 900.
404webhook_not_configuredNo webhook has been configured for the watch.
409watch_disabledThe watch was disabled; re-enable it with PATCH.
422invalid_selectorThe CSS selector cannot be parsed.
422selector_not_foundNo element matched the selector on the fetched page.
422target_not_allowedThe URL resolves to a private or disallowed address.
502http_status_errorThe target returned a non-2xx HTTP status.
429monthly_check_limit_exceededThe monthly check allowance is used up.