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 webhooks —
license.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.
Recommended cadence¶
| 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 |
|---|---|---|
active → grace |
Subscription overdue but still in grace. | Keep running; soft warning + ask the user to check their account. |
grace → active |
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.