Skip to content

Heartbeat Protocol

Forge performs a weekly license health check (heartbeat) to verify that your license is still active — not revoked due to a refund, payment failure, or subscription cancellation. This page documents exactly what is sent, when, and how to inspect or control the behavior.

Air-gapped binaries (compiled with the air_gapped feature) never perform any heartbeat. All network code is compiled out entirely in those builds.


The heartbeat sends a single JSON object via HTTP POST:

{
"license_hash": "a3f1b2c4d5e6...",
"client_version": "1.3.0",
"platform": "linux-x86_64",
"team_id": null
}
FieldValueNotes
license_hashSHA-256 hex of the raw license key stringThe license key itself is never sent — only its hash. This is how the server identifies the license without seeing the plaintext.
client_versionForge version string (e.g., "1.3.0")Used by the server for telemetry on version adoption.
platform"linux-x86_64", "macos-aarch64", "windows-x86_64", etc.Operating system and CPU architecture.
team_idTeam subscription ID, or nullIncluded for Team licenses so the admin portal can show aggregate team activity (“4 of 5 seats active this week”). null for Solo and Pro.

What is never transmitted: source code, file paths, queries, repository names, symbol names, email addresses, or any personal data. Privacy policy: https://forge.ironpinelabs.com/privacy#heartbeat


POST https://forge-license-webhook.ironpinelabs.workers.dev/heartbeat

The endpoint is a Cloudflare Worker. It logs the hash, version, and platform in a D1 database for support and analytics. It does not store your source code or queries. The endpoint can be overridden for testing via the FORGE_HEARTBEAT_URL environment variable (internal use only).


The heartbeat fires from the background task spawned by forge serve:

  • Base interval: 7 days
  • Jitter: up to ±12 hours (uniform random, applied at each scheduling point)
  • First run: immediately on forge serve startup if no heartbeat has been attempted yet, or if the next scheduled attempt is in the past

The jitter prevents all instances updating simultaneously (“thundering herd”). The exact next attempt time is stored in ~/.forge/heartbeat.json.


The server responds with a cached_until timestamp, typically last_heartbeat_at + 14 days. Forge uses this to avoid unnecessary network calls:

Cache ageForge behavior
Within cached_untilUse cached result; do not attempt a new heartbeat
cached_until elapsed but < 30 days since last heartbeatAttempt a new heartbeat on next forge serve startup
> 30 days since last heartbeatWarn on startup: “License check overdue — please ensure network access”
> 60 days since last heartbeatDegrade to Community Mode regardless of cached license status

The 60-day grace window ensures legitimate customers are never permanently locked out by a prolonged network outage.


The heartbeat response includes a status field:

StatusForge behavior
activeContinue normally
revokedDegrade to Community Mode on next forge serve startup; show revocation message
expiredDegrade to Community Mode; show renewal prompt
unknownLog a warning; continue with cached license (the server may not have the hash if the key was issued before v1.3.0)

Degradation is never immediate — it takes effect at the next forge serve startup after the heartbeat response is received and cached.


You can inspect and control the heartbeat at any time:

Terminal window
forge config heartbeat show

Prints:

  • Last heartbeat timestamp
  • Cached result (status + cached_until)
  • Next scheduled attempt
  • Exact payload that would be transmitted
  • Link to the privacy policy

Nothing is sent by running this command.

Terminal window
forge config heartbeat now

Bypasses the weekly schedule and sends a heartbeat immediately. Use this after re-subscribing or after a network outage to restore the cache. Exits non-zero if the heartbeat fails.


Heartbeat state is persisted to ~/.forge/heartbeat.json:

{
"last_heartbeat_at": "2026-04-15T10:00:00Z",
"last_status": "active",
"cached_until": "2026-04-29T10:00:00Z",
"last_error": null,
"next_attempt_at": "2026-04-22T09:17:00Z"
}
FieldDescription
last_heartbeat_atUTC timestamp of the last successful heartbeat.
last_statusStatus returned by the last successful heartbeat: active, revoked, expired, or unknown.
cached_untilThe server’s suggested cache expiry.
last_errorError message from the most recent failed attempt, or null.
next_attempt_atJittered timestamp of the next scheduled attempt.

All fields are optional. If the file is missing or corrupt, Forge resets to a clean default state (which triggers an immediate heartbeat attempt on the next forge serve startup).


If your ~/.forge/license.json has key_hash: "<unknown>" (activated before Forge v1.3.0), the heartbeat is skipped until you re-activate:

Terminal window
forge activate <your-license-key>

Re-activation writes the correct key_hash and enables heartbeat going forward. Your license remains valid in the meantime; this only affects the server-side activity tracking.


Air-gapped binaries are compiled with --features air_gapped. In these builds:

  • The reqwest HTTP client is never called (though it remains linked in the binary in v1.3.0; true network-code removal is planned for v1.4.0)
  • forge config heartbeat show displays an air-gapped mode notice instead of the standard payload display
  • forge config heartbeat now is a no-op
  • License validation is performed entirely against the embedded Ed25519 signature and the compile-time expiration year

No configuration is needed for air-gapped builds; heartbeat is disabled at compile time.