Switching Your Act-On API Integration from a Password to Browser Sign-In

If your integration calls the Act-On API by sending a username and password to POST /token (grant_type=password), you can switch it to a one-time browser sign-in. Today the integration stores an Act-On user's password and sends it every time it needs a token. After the change, a person signs in once in a browser on Act-On's sign-in page, with multi-factor authentication (MFA) if their account requires it. The integration receives a refresh token from that sign-in and uses it from then on to get new access tokens, with no password and no person involved. This is the OAuth 2.0 "authorization code" flow.

This article is for the developer or IT person who owns the integration.

Why this matters

  • No stored password. The integration never sees or keeps the user's password.
  • Works with MFA. A password-based token request can't complete an MFA challenge, because there is no person to answer it. With the browser sign-in, the person completes MFA once, during that sign-in. Act-On's sign-in applies MFA to the browser sign-in and not to refresh-token requests, so the integration keeps running unattended afterwards.
  • Access can be cut off without a password change. Revoking the refresh token stops the integration, and the user's own sign-in is unaffected.
  • The integration doesn't break when the user changes their password.

Quick Reference

  • Sign in once: open /authorize in a browser and sign in as the integration's user.
  • Exchange the code: send it straight away to POST /token with grant_type=authorization_code.
  • Refresh: POST /token with grant_type=refresh_token. Access tokens last 1 hour.
  • Rotation: save the new refresh token from every refresh. Never use a refresh token twice.
  • Recovery: nearly every refresh-token failure is fixed by repeating the browser sign-in.

Not supported: app-only access ("client credentials") and static API keys. Every Act-On API token must belong to a user.

Before You Start

You need four things.

  1. Your Act-On API app's client ID and client secret. These are the same ones the integration uses today.
  2. A callback (redirect) URL registered on that app. After sign-in, Act-On's sign-in page sends the browser back to this URL with a one-time code. The URL must be registered on your API app in advance, and customers can't register it themselves today. Request it through: [Support? see Q1]. Give the exact URL, including scheme, host, port and path, for example https://integrations.example.com/acton/callback.
    • Important: The redirect_uri you send must match a registered URL exactly. If it doesn't, the sign-in page shows "Callback URL mismatch" and no code is issued.
    • A back-end job with no web interface can register a local address such as http://localhost:8080/callback and run a small one-off script for the sign-in (see Step 3).
  3. The user the integration will act as (see Which User to Sign In As).
  4. Somewhere secure to store the refresh token, which the integration can both read and write. It is a long-lived credential: treat it like a password.

The One-Time Setup

Step 1: Send a person to the sign-in page

Open this URL in a browser:

https://api.actonsoftware.com/authorize?response_type=code&client_id=<CLIENT_ID>&redirect_uri=<URL-ENCODED REDIRECT URI>&state=<RANDOM VALUE>

EU customers use https://api-eu.actonsoftware.com/authorize with the same parameters. Both send the person to the same Act-On sign-in page.

ParameterRequiredValue
response_typeYescode
client_idYesYour API app's client ID
redirect_uriYesA registered callback URL, URL-encoded
stateRecommendedA random value your integration generates and checks when the browser comes back (protects against forged callbacks)

Don't send scope or audience. Act-On sets both itself and ignores any you send. It always asks for offline_access, which is what makes a refresh token come back.

Step 2: The person signs in

The person signs in on Act-On's sign-in page as the integration's user. If MFA is required for that user, they complete it here, and they are asked to enrol first if they haven't yet.

Step 3: The browser comes back with a code

The browser is redirected to:

<redirect_uri>?code=<CODE>&state=<STATE>
  • Check that state matches the value you sent.
  • Important: The code works once and only for a short time, so exchange it straight away (Step 4). Sending it a second time fails with invalid_grant "Invalid authorization code".
  • For a back-end job: a one-off script can listen on the registered localhost address and catch the code. Or the person can copy the code value from the browser's address bar after the redirect, even if the page itself shows an error because nothing is listening, and paste it into the exchange step quickly.

Step 4: Exchange the code for tokens

curl -s https://api.actonsoftware.com/token \
  -d grant_type=authorization_code \
  -d code=<CODE> \
  --data-urlencode redirect_uri=<REDIRECT URI> \
  -d client_id=<CLIENT_ID> \
  -d client_secret=<CLIENT_SECRET>
  • The client ID and secret can go in the form body, as above, or in an HTTP Basic Authorization header. Both work.
  • Important: redirect_uri must be the same value used in Step 1, or the exchange fails (see Troubleshooting).

Response (values shortened):

{
  "access_token": "eyJhbGciOi...",
  "refresh_token": "v1.MjE3...",
  "scope": "offline_access",
  "expires_in": 3600,
  "token_type": "Bearer"
}
  • access_token is valid for 1 hour (expires_in is in seconds).
  • Store refresh_token securely now. This is the credential the integration keeps.

Step 5: Call the API

Send the access token exactly as today: Authorization: Bearer <access_token>. Nothing else about the API calls changes.

Getting New Access Tokens

When the access token expires (after an hour, or when the API returns 401), get a new one with the refresh token. No person is involved.

curl -s https://api.actonsoftware.com/token \
  -d grant_type=refresh_token \
  -d refresh_token=<REFRESH_TOKEN> \
  -d client_id=<CLIENT_ID> \
  -d client_secret=<CLIENT_SECRET>

The response has the same shape as in Step 4.

Refresh Token Rules

Important: These are the standard settings on Act-On customer API apps. Rules 1 and 2 are where integrations break.

  1. Every refresh returns a new refresh token, and the old one stops working ("rotation"). The integration must save the new refresh_token from every refresh response, replacing the old one, before using the new access token.
  2. Never use a refresh token twice, and never refresh from two places at once. Act-On allows no grace period for reusing an old refresh token. Reusing one is treated as a possible theft, and the whole chain is cancelled: the newest refresh token stops working too, and the person has to do the browser sign-in again. In practice:
    • Keep one copy of the refresh token.
    • Refresh from one process at a time (use a lock, or one scheduled job).
    • If a refresh request fails after it was sent (a timeout or dropped connection), the integration can't know whether Act-On rotated the token. Retrying with the old token cancels the chain if it was rotated. The recovery is a new browser sign-in.
  3. A refresh token not used for 30 days expires. There is no overall limit, so a token refreshed at least once every 30 days lasts indefinitely. An integration that runs less often than monthly should refresh on a schedule, or plan for a new sign-in.
  4. Access tokens last 1 hour. Reuse an access token until it expires; don't refresh before every call.

Which User to Sign In As

  • The integration acts as the user who signs in. Its calls have that user's access, and it stops working when that user is removed or their sign-in is disabled.
  • Use a dedicated integration user, or at least a long-lived one, not a person who might leave or change role.
  • The person who does the one-time sign-in must be able to sign in as that user, including its MFA if required.

Moving an Existing Integration Over

  1. Get the callback URL registered (see Before You Start).
  2. Change the integration's token handling: store a refresh token instead of the password, use the refresh call (see Getting New Access Tokens), and follow the rotation rules.
  3. Do the one-time sign-in and store the refresh token.
  4. Run the integration and confirm its calls succeed.
  5. Remove the stored password from the integration. The user's password can then be changed and MFA turned on without affecting the integration [see Q3].

Troubleshooting

"Token request" means POST /token. Errors from it are shown as the HTTP status, the error value and the error_description text.

What you seeWhereWhyWhat to do
Sign-in page shows "Callback URL mismatch. <url> is not in the list of allowed callback URLs"Browser, Step 1redirect_uri isn't registered on your API app, or differs from the registered value (scheme, host, port, path, trailing slash).Use the registered value exactly, or ask to have the new one registered.
403 unauthorized_client
"The redirect URI is wrong. You sent …, and we expected …"
Token request, Step 4The redirect_uri in the exchange differs from the one used at /authorize.Send the same value in both steps, then repeat the sign-in to get a fresh code.
403 invalid_grant
"Invalid authorization code"
Token request, Step 4The code was already used, or it expired.Repeat the browser sign-in and exchange the new code straight away.
401 access_denied
"Unauthorized"
Token requestWrong client ID or client secret.Check the app's credentials.
403 invalid_grant
"Unknown or invalid refresh token."
Token request, refreshThe refresh token is unknown, expired (30 days unused), already used (rotation), or revoked. Reusing an old token also cancels the newest one, so after a reuse every saved token fails this way (rule 2).Repeat the browser sign-in and store the new refresh token. Then find out why: an old token was saved, two processes refreshed at once, or the integration was idle for more than 30 days.
Refresh fails after the user's password was changed, MFA was reset, or the user was removed or blockedToken request, refresh[Expected; see Q3–Q5]Repeat the browser sign-in as a valid user. If the user was removed, choose a new one (see Which User to Sign In As).
401 on API calls after a successful refreshAPI callsThe access token wasn't the newest one, or the user lost access to the account.Use the access token from the latest refresh. If it persists, check the user's access in Act-On.

General rule for recovery: nearly every refresh-token failure is fixed by repeating the one-time browser sign-in and storing the new refresh token.

Was this article helpful?

Have more questions? Submit a request