Free guide · Francisco Arrieta · 4 min

Build a cron job that watches something

Build a scheduled job in Go that watches a page or price and messages you when it changes

A small program that checks a page every few minutes and messages you the moment something changes. No dashboard, no third-party monitoring service, just a loop and a webhook.

Price trackers, status-page watchers, “tell me when this is back in stock”, they’re all the same shape: fetch something, compare it to last time, say something if it’s different.

What you’ll need

  • Go installed
  • A Discord server you can add a webhook to (free, takes a minute, or swap in Slack’s equivalent)
  • About 30 minutes

Step 1

Get a webhook URL

In Discord: server settings → Integrations → Webhooks → New Webhook → copy the URL. That URL is the whole “notify me” mechanism, anything that sends it a POST request shows up as a message.


Step 2

The check, run once

mkdir watcher && cd watcher
go mod init watcher

Create main.go:

package main

import (
	"crypto/sha256"
	"fmt"
	"io"
	"net/http"
)

func fetchHash(url string) (string, error) {
	resp, err := http.Get(url)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}

	hash := sha256.Sum256(body)
	return fmt.Sprintf("%x", hash), nil
}

func main() {
	hash, err := fetchHash("https://example.com")
	if err != nil {
		fmt.Println("error:", err)
		return
	}
	fmt.Println("current hash:", hash)
}

This doesn’t compare a specific price or word, it hashes the entire page. Any change to the page content changes the hash. Cruder than checking one specific number, but it works on any page with zero configuration, and that’s the right trade for a first version.

go run .

You’ll see a hash. Run it again, same hash, since the page hasn’t changed.


Step 3

Check on a schedule

Add a loop with time.Ticker:

package main

import (
	"crypto/sha256"
	"fmt"
	"io"
	"net/http"
	"time"
)

func fetchHash(url string) (string, error) {
	resp, err := http.Get(url)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}

	hash := sha256.Sum256(body)
	return fmt.Sprintf("%x", hash), nil
}

func main() {
	url := "https://example.com"
	lastHash := ""

	ticker := time.NewTicker(2 * time.Minute)
	defer ticker.Stop()

	for {
		hash, err := fetchHash(url)
		if err != nil {
			fmt.Println("error:", err)
		} else if lastHash != "" && hash != lastHash {
			fmt.Println("CHANGED:", url)
		} else {
			fmt.Println("no change")
		}

		if hash != "" {
			lastHash = hash
		}

		<-ticker.C
	}
}

Run it and leave it running. Every two minutes, it checks, and prints whether anything changed. Two minutes is short for testing, real use is usually every 15 to 60 minutes, depending on how urgently you need to know.


Step 4

Message yourself instead of printing

Replace the fmt.Println("CHANGED:", url) line with an actual notification:

func notify(webhookURL, message string) error {
	payload := fmt.Sprintf(`{"content": %q}`, message)
	resp, err := http.Post(webhookURL, "application/json", strings.NewReader(payload))
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	return nil
}

Add "strings" to the imports, and in main, define your webhook URL near the top:

webhookURL := "YOUR_DISCORD_WEBHOOK_URL"

Then where the guide currently prints CHANGED:, call it instead:

} else if lastHash != "" && hash != lastHash {
	notify(webhookURL, fmt.Sprintf("Changed: %s", url))

Change the URL you’re watching to something that updates often enough to test against, or just edit the file being watched if you’re testing locally. Watch your phone. The message arrives.


What to point this at

A competitor’s pricing page, hashed as a whole, tells you the moment anything on it moves.

A status page, watched for the specific word “operational” going missing, is more useful than a full-page hash, since status pages change timestamps constantly even when nothing real happened. That’s a small edit: check for a string in the body instead of hashing the whole thing.

Job listings, event pages, “back in stock” pages, same pattern, different target.


What this doesn’t do

It runs as long as your terminal does. Close it, the watching stops. Turning this into something that survives a reboot and runs quietly in the background is a deployment question, not a coding one, covered by running it as a background service or shipping it to a small always-on machine.

A full-page hash is blunt. Ad rotations, timestamps, and “you might also like” widgets change the hash even when the thing you actually care about hasn’t. Watching a specific piece of the page, not the whole thing, is the fix, and it depends entirely on that page’s structure.


The short version

  1. Get a Discord (or Slack) webhook URL, that’s your notification channel
  2. Fetch a page, hash the body, that’s your “did it change” signal
  3. Loop it with time.Ticker, comparing each hash to the last one
  4. On a change, POST to the webhook instead of printing
  5. Point it at something that actually matters to you, and narrow the check from “whole page” to “one specific thing” once you know what you’re watching for

Sources

Written August 2026.

Prints to PDF from your browser — colours and all.