A user disconnects your meal planner at their grocery-list provider — how does your server find out?
answer
- no notification arrives
- the refusal is the notification
- invalid_grant on refresh is terminal
- stop the job, keep the row
- re-authorize when the user next needs it
basics
~20 sNo notification arrives. The next refresh of that user's stored grant is refused with invalid_grant, or an API call answers 401, and that refusal is terminal: mark the row dead and stop the work rather than retrying.
solid answer
~50 sThe refusal is the notification. A user revoking a standing authorization at the provider produces no inbound message to your application, so the first you know of it is a request being turned down: `400` with `invalid_grant` when you try to refresh, or `401` with `invalid_token` on an API call whose token has also been killed. The important part is classification. `invalid_grant` means the grant is gone — mark the row dead with a reason and a timestamp, clear the stored token columns, and stop scheduling work for it, because retrying is a request whose answer will never change. A timeout or a `5xx` is transient and does deserve backoff, and `403` with `insufficient_scope` means the grant is alive but narrower than the call. Then put the user in front of authorization again the next time they ask for something that needs it.
code
http · 15 linesPOST /oauth2/token HTTP/1.1
Host: auth.grocery-list-provider.example
Content-Type: application/x-www-form-urlencoded
Authorization: Basic <client credential>
grant_type=refresh_token&refresh_token=8xLOxBtZp8
HTTP/1.1 400 Bad Request
Content-Type: application/json
Cache-Control: no-store
{
"error": "invalid_grant",
"error_description": "authorization revoked by the resource owner"
}go deeper
Remember that no message arrives when a user disconnects an application; the first sign is a refused refresh, and the answer will be the same however often you retry it.
Distinguish the refusals: invalid_grant on refresh is terminal, 401 on an API call may only mean an aged access token, and 403 with insufficient_scope means the grant is alive but narrower.
Describe the state machine around the row — mark, clear the token columns, drop it from the sweep, keep something the interface can show — and say why a single refusal is not an alert while a cliff of them is.
Treat it as a product decision as much as an engineering one: how a disconnection is surfaced, when re-authorization is worth interrupting someone for, and how long a dead grant's record is kept.
## Why nothing arrives A standing authorization to call somebody else's API on a user's behalf is a record at the provider, and the user can withdraw it from a screen you never see. Your application is not a party to that action, and for a revoked standing grant no message is sent to you. So discovery is passive. You learn by being refused, which means the quality of the integration is decided by how well it classifies refusals — not by how quickly it notices, because it cannot notice at all. ## Where the refusal shows up | what you see | where | what it means | what to do | |---|---|---|---| | `400` with `invalid_grant` | the provider's token endpoint, on refresh | the grant is gone: revoked, expired, or the refresh token was superseded | terminal — mark the row dead, stop scheduled work | | `401` with `invalid_token` | the provider's API | this access token is not usable; it may only have expired | refresh once; if that refresh is refused, the row above applies | | `403` with `insufficient_scope` | the provider's API | the grant is alive but narrower than this call needs | not a revocation — needs a new authorization for a wider scope set | | timeout or `5xx` | either | the provider is unwell | retry with backoff and leave the row alone | The two rows people conflate are the middle ones. `401` says *I do not know that this credential is good*; `403` says *I know who you are and this grant does not cover it*. Treating the second as revocation deletes working grants; treating the first as revocation deletes grants whose access token had merely aged out. ## Terminal versus transient is the whole decision Everything else follows from getting one boolean right: - **Terminal** means no retry, no backoff, no queue. The answer is stable. Retrying an `invalid_grant` once a minute produces a stream of errors that trains everyone to ignore the alert. - **Transient** means retry with backoff. A provider having a bad hour answers slowly or with `5xx`, not with `invalid_grant`. The one caveat worth holding: a *single* `invalid_grant` is about that user, but a sudden cliff of them across many unrelated users at once is usually about **you** — a client credential that changed, a key rotation that lost the ability to decrypt the stored column, a deployment pointed at the wrong registration. That pattern is worth paging on; an individual refusal is not. ## What the application does at that instant 1. **Classify** the refusal from the status and the error value, not from the text of the message. 2. **Mark the row**, with a reason and a timestamp, so the state is queryable later. 3. **Clear the stored token columns.** They are ciphertext of a credential that no longer works; keeping them is risk without use. 4. **Stop the background work quietly.** Remove the row from the sweep and cancel whatever was scheduled against it. A user exercising a self-service control is not an incident and must not wake anybody. 5. **Keep the record.** The interface needs something to show — *disconnected, reconnect to resume* — and support needs something to read. Silent deletion turns a comprehensible failure into a mystery. 6. **Re-authorize at the next user-present moment** that actually needs the grant. Do not start a redirect out of a background job, and do not nag someone who has not asked for the feature. ## What not to do - Do not poll the provider to see whether the grant is still alive; a spent call tells you only what the next real call would have. - Do not retry a dead grant on the same backoff ladder as a timeout. - Do not delete the row the instant you see the refusal, leaving the user with a feature that stopped for no stated reason. - Do not mark a row dead on `403`; the grant is alive and the fix is a wider authorization, not a reconnection. ## The shape of the good answer Say plainly that nothing notifies you, name the two places the refusal appears, and be precise about which one is terminal. Then describe what happens to the row, to the schedule, and to the user's next visit. An interviewer asking this is checking whether you have ever owned an integration that quietly stopped working for a subset of users — and whether you would be able to explain to them why.
- A refresh is refused with invalid_grant during a provider incident — is the grant really dead?For that row, treat it as dead: `invalid_grant` is specified for a grant that is invalid, expired or revoked, and a provider having an incident usually answers `5xx` or a rate-limit response instead. The pattern worth investigating is a cliff of refusals across many unrelated users at once, which normally means something changed on your side — the client credential, the encryption key, or the registration a deployment points at.
- When a user disconnects inside your own application, is deleting the stored row enough?No. Deleting the row ends your ability to use the grant but leaves the authorization standing at the provider, where it still appears in that user's list of connected applications. Ask the provider to revoke it as well, then clear the row — and make the local clear the step that happens regardless, so a failed call to the provider does not leave you holding usable tokens.
- How long do you keep a row you have marked dead?Keep the row while it still explains something — the interface saying the planner is disconnected, and any audit question about when access stopped — and clear it on a stated retention rule. Do not keep the token columns for any of that time: null them at the moment you mark the row, because ciphertext of a dead credential is pure liability.
Someone changes the lock on their own front door. Nobody calls to tell the cleaner; the cleaner finds out when the key will not turn, and the only sensible response is to stop coming and ask for a new key rather than to keep trying the old one.
saying these in an interview costs you the question
- Expects the provider to call back when a user disconnects the application
- Retries an invalid_grant refusal with exponential backoff like a timeout
- Pages on-call for a user exercising a self-service control
- Deletes the grant row silently, so nothing explains why a feature stopped
- Treats 403 insufficient_scope as proof the user revoked the grant
- Polls the provider to check whether each stored grant is still alive