Self-hosted On-demand Workers API

This page described the protocol used with the Self-hosted On-demand Workers -feature to help in the development of custom On-demand Workers Provisioners.


All requests sent by the Control Room to the On-demand Workers Provisioner are authenticated with an HMAC algorithm that relies on a secret that is shared between the two parties. The implemented algorithm is loosely based on the Signature Version 4 Signing process used by Amazon on AWS. The Calculated signature is in header x-rc-signature

In addition to the signature, a timestamp is included in the headers and the recipient must verify the timestamp is recent to protect against replay attacks.


The following are the supported commands for the On-demand Workers API. Control Room sends these commands to the HTTP(S) endpoint configured into the Control room and signs them with the HMAC algorithm described above.

  • Both responses and requests with payload must be sent in application/json content type.
  • The On-demand Workers Provisioner must verify the authenticity of the message by verifying the HMAC signature was generated with the shared secret
  • The On-demand Workers Provisioner must verify the timestamp of the message was generated within 15 minutes of receiving the request
  • Control room must retry a request if the response code is 500
  • Control room may not retry a request if the response code is 400,403

Common HTTP status codes

The On-demand Workers Provisioner must implement the sending of the following status codes for all requests.

Status codeDescription
400Request not recognized
403HMAC check failed

Start Command

When receiving a start command:

  • On-demand Workers Provisioner must start a runtime
  • The runtime must link itself to the cloud using the runtimeLinkToken given in the request
  • The runtime must assume the runtime Id given in the request in the field runtimeId.


typestringvalue is start
workspaceIdstringworkspaceId of robot
runtimeLinkTokenstringSingle use link token that the started runtime should use to link to control room
runtimeIdstringruntimeId that the runtime should assign itself.
maxLifetimeSecondsnumberMax lifetime of runtime in seconds. Provisioner may stop runtime if runtime is active for longer than this time.


The On-demand Workers Provisioner must implement the following status codes in addition to the common status codes.

Status codeDescription
200Runtime Started
500Runtime creation failed

Stop Command

When receiving a stop command:

  • The On-demand Workers Provisioner may stop the runtime execution


typestringvalue is stop
workspaceIdstringworkspaceId of robot
runtimeIdstringruntimeId that the runtime should assign itself.


The On-demand Workers Provisioner must implement the following status codes in addition to the common status codes.

Status codeDescription
200Runtime stopped or command not implemented
500Runtime stopping failed

Status Command


typestringvalue is status


versionnumbervalue is 1
statusstring'OK' if everything is ok, error message otherwise

Example implementation of Authentication in Python

The following is an example implementation of the HMAC and timestamp verification written in Python.

from http.server import BaseHTTPRequestHandler, HTTPServer
import logging
import hashlib
import base64
import hmac
from time import time

class HmacAuthenticatingServer(BaseHTTPRequestHandler):
    def do_POST(self):
        content_length = int(self.headers['Content-Length']) # <--- Gets the size of data
        post_data = # <--- Gets the data itself

        # shared secret
        secret = 'secret'

        # variables
        algorithm = 'sha256'
        method = 'POST'
        uri = self.path
        querystring = ''

        # values from headers
        signature_from_header = self.headers['x-rc-signature']
        timestamp = self.headers['x-rc-timestamp']
        signed_headers = self.headers['x-rc-signed-headers']

        headers = []
        for header in signed_headers.split(';'):
            headers.append(header + ':' + self.headers[header])

        # body checksum
        payload_hash = base64.b64encode(hashlib.sha256(post_data).digest()).decode('utf-8')

        # construct string that represents request and hash it
        request = method + '\n' + uri + '\n' + querystring + '\n' + '\n'.join(headers) + '\n' + signed_headers + '\n' + payload_hash
        request_hash = base64.b64encode(hashlib.sha256(request.encode('utf-8')).digest()).decode('utf-8')

        # construct string that represents algorith, timestamp and request hash
        string_to_sign = algorithm + '\n' + timestamp + '\n' + request_hash

        # sign the string with the shared secret
        signature ='utf-8'), (string_to_sign).encode('utf-8'), hashlib.sha256).hexdigest()

        # Verify the signature matches
        signature_valid = signature == signature_from_header;

        # Verify the timestamp is recent
        timestamp_current = int(time())
        timestamp_delta = abs(timestamp_current - int(timestamp));
        timestamp_valid = timestamp_delta < 15*60; # 15 minutes

        authenticated = signature_valid and timestamp_valid

        print('authenticated: ', authenticated)

        self.send_response(200 if authenticated else 403)

def run(server_class=HTTPServer, handler_class=HmacAuthenticatingServer, port=8080):
    server_address = ('', port)
    httpd = server_class(server_address, handler_class)'Starting httpd...\n')
    except KeyboardInterrupt:
    httpd.server_close()'Stopping httpd...\n')

if __name__ == '__main__':
    from sys import argv
    if len(argv) == 2:

November 18, 2021