Skip to content

Heartbeat & offline grace

The licence sync model is pull: the product calls POST /v1/validate periodically to refresh the token and learn about status changes (subscription renewed, suspended, revoked, entitlements changed). This is the source of truth and must be implemented.

Berlanggan can also push outbound webhookslicense.issued / license.renewed / license.suspended — when the seller configures a webhook URL on the product. Treat those as low-latency hints that let you react faster; they never replace POST /v1/validate.

Condition Validate frequency
Normal Once a day, or on app start.
Near token_expires_at Speed up to every few hours.
After a network failure Exponential backoff: 5m → 15m → 1h → …
status = rate_limited Longer backoff; don't retry tightly.

Always replace your local token with the token from the latest response.

Handling an offline product

grace_days in the response states how many days the product may keep working without a successful server contact after token_expires_at passes.

Recommended product-side logic:

token_exp    = token_expires_at from the last response
grace_days   = grace_days from the last response

if now <= token_exp:
    RUN NORMALLY
elif now <= token_exp + grace_days:
    RUN NORMALLY + show "needs to be online to verify the licence"
else:
    RESTRICT until validate succeeds again

Store token, token_expires_at, grace_days, and entitlement (with its signature) locally and encrypted so the decision above can be made without the network.

Status changes to handle

From the response Meaning Action
activegrace Subscription overdue but still in grace. Keep running; soft warning + ask the user to check their account.
graceactive Paid/renewed. Clear the warning.
→ suspended / expired Access revoked temporarily / ended. Lock paid features.
→ revoked Permanently revoked. Lock; offer re-activation if the user thinks it's wrong.
entitlements changed Plan upgraded/downgraded. Apply the new limits/features on the next cycle.

Network failure ≠ invalid licence

Distinguish clearly:

  • HTTP error / timeout → don't change status; use the last trusted state and honour grace_days.
  • HTTP 200 with a non-active status → the official status changed; apply it.

Deactivate on uninstall

Call POST /v1/deactivate when the user uninstalls or moves the installation, so the seat is freed immediately and the user doesn't have to release it manually from the dashboard.