Auth for HTTP APIs

To use an HTTP API such as a trigger or getting device info, you'll need to get a valid authentication token. This is so an HTTP request (i.e., trigger) can come from only the parties you authorize.


Utilities Available

Included in the Relay SDK are utility functions that makes it easy to create and send an HTTP trigger or send an HTTP device info request, which are the two common items that require HTTP authentication. These utilities hide the network operations and authentication mechanics, making it much easier for you. The content below describes how to do it manually, but you'll probably want to use these utility methods instead and can stop reading here. See the triggerWorkflow and fetchDevice methods in the "Special Utilities" section on the Utilities page. If you are doing something different, such as fetching analytics or configuring NFC tags, then you'll need to keep on reading below.

As a quick background, Relay uses two kinds of tokens: refresh_token and access_token. Ultimately, you'll want to use an access_token when accessing an HTTP API, it goes in the Authorization header. An access_token has a limited lifetime (single-digit hours) before it expires. When an access_token does expire, you need to generate a fresh new one from a refresh_token. The refresh_token has an unlimited lifetime, and is used only to create an access_token. Both should be treated with care to prevent unauthorized parties from seeing them, but in particular the refresh_token, because it has an unlimited lifetime.

To start, let's assume you've already registered an HTTP workflow:

$ relay workflow create http --trigger=POST --name my_http_wf \
    --uri wss:// --install 99000756034567


Relay CLI Versions

The token:generate command is currently available only in the beta branch of @relaypro/cli. You may need to update it by running npm install -g @relaypro/cli@beta.

To start, use the CLI to generate a refresh token:

$ relay token generate
Opening browser to login
relay: Waiting for login... done
The following token can be used in the configured environment
env:   pro
token: 8bf427df18d88ce4e16ea812b9dXXXX

When prompted to login via the web, make sure you check the 'Remember me' button. You may need to sign out before logging in again.


Great, we've got a refresh token! This is a permanent refresh token and can be used to generate access_tokens indefinitely, as each access_token has a limited lifetime. Don't share the refresh token with anyone!! Next, let's grab the environment ID & auth server:

$ relay env
auth_cli_id: 4EgeETYm
auth_sdk_id: RJZKRhh9
env:         pro

Define API and Auth Hosts using shell environment variables:

# Auth
RELAY_ENV=pro                              # default

# API      # default

Now you can use that refresh token to generate an access_token from the auth server. Access tokens are valid for 2 hours. You can generate an access token either from the command line or using your favorite framework, for Node we like axios. For the path part of the auth server URL, use /oauth2/token.

curl -vv -d "grant_type=refresh_token&client_id=RJZKRhh9&refresh_token=8bf427df18d88ce4e16ea812b9dXXXX" \
    -H "Content-Type: application/x-www-form-urlencoded" \
    -X POST
async function refresh_auth() {
  try {
    const response = await, new URLSearchParams({
      'grant_type': 'refresh_token',
      'client_id': 'rGGK996c',
      'refresh_token': process.env.REFRESH_TOKEN
      headers : {
        'Content-Type': 'application/x-www-form-urlencoded'
  } catch (err) {

This access_token is what you will use in the auth header for your HTTP trigger request.

If using curl above, in the returned JSON document, use the access_token value, not the id_token value.


When to Create Access Tokens

When getting an access_token for the Authorization header of your http trigger, the best practice is to avoid creating a new access token for each trigger invocation. Instead, cache the access token from previous use, and create a new access token only when its use returns a 401 code.

Now you have almost everything you need. One missing piece is the id of the workflow, which you can obtain via the CLI:

$ relay workflow list
=== Installed Workflows

 ID                                     Name         Type             
 ────────────────────────────────────── ──────────── ──────────────── 
 wf_http_Y4xdLSLW8gJIpGaCjqOSp3         my_http_wf   http:POST

The last missing piece is the subscriber id, which you can also obtain via the CLI:

$ relay whoami
=== You are

Name:               Alice Rodriguez
Email:              [email protected]
Default Subscriber: ee4547e0-aaef-4956-b853-ad0123456789
Auth User ID:       98071da3-572b-4552-be88-8907a9ad03ef
Relay User ID:      VIRT1cMBYVBIPZ3kR7aoIsIjmY
Capabilities:       workflow_sdk: true, indoor_positioning: true

If you don't already know the device id, you can list all of them via the CLI:

$ relay devices

And if you need to see the corresponding name for each of these device ids, you can get that list in Dash in the Account -> Users view.

Now you can send the trigger, using the access token, targeting a device as user_id.

The path part of the URL is /ibot/workflow/ followed by the workflow id, and then with the query parameters subscriber_id and user_id (the device id).

In the example using Node and Axios, we are using interceptors to refresh the access token when it expires and invoking a custom HTTP Trigger.

The Relay server will require a User-Agent header. It doesn't matter what the value is, it simply needs to be present and non-empty.

HTTP trigger:

curl -vv -d '{"action": "invoke"}' \
    -H "Content-Type: application/json" \
    -H "Authorization: Bearer 3c388bb1e69244ec2fc5e2209df3d1cc" \
    -H "User-Agent: my_client" \
import axios from 'axios'
const _axios = axios.create()

const access_token = await refresh_auth()
_axios.defaults.headers.common['Authorization'] = `Bearer ${access_token}`

_axios.interceptors.response.use(function (response) {
  return response;
}, async function (error) {
  if(error.response) {
  let originalRequest = error.config
  if (error.response.status === 401 && !originalRequest._retry) {
    originalRequest._retry = true
    const token = await refresh_auth()
    _axios.defaults.headers.common['Authorization'] = `Bearer ${token}`
    return _axios(originalRequest)
  return Promise.reject(error)

try { 
  const response = await`${process.env.RELAY_HOST}${process.env.WORKFLOW}?subscriber_id=${sub_id}&user_id=${user_id}`,
    "action": "invoke"
  if (response.status == 200) {
    console.log(`Remote trigger invoked`)
} catch (err) {

Fetching device info:

curl -vv -X GET \
    -H "Authorization: Bearer 3c388bb1e69244ec2fc5e2209df3d1cc" \
    -H "User-Agent: my_client" \

You may have noticed in the above two examples that there is a User-Agent header in our outbound request. Why is that there? The firewalls around the Relay server require that User-Agent header to be present, but it doesn't matter what the value of it is, it just needs to be non-empty.

This should be everything you need to know to fully authenticate for using the HTTP APIs.