Overview

Every request must be signed using your API key and API secret. The signature covers the HTTP method, URL path, query string, timestamp, and a hash of the request body — so intercepted requests cannot be replayed and the body cannot be tampered with.

Required headers

HeaderValue
Authorization SQRv1 Credential=<key>, Timestamp=<unix_seconds>, Signature=<hex>
X-SQR-Content-Hash Lowercase hex SHA-256 of the raw request body. For requests with no body, use the SHA-256 of the empty string.
Content-Type application/json for requests that include a body.
The timestamp must be within ±5 minutes of the server clock. Requests outside that window are rejected to prevent replay attacks.

Signing algorithm

Build the canonical string by joining these five values with newlines (\n):

METHOD\n
PATH\n
SORTED_QUERY_STRING\n
UNIX_TIMESTAMP_SECONDS\n
X_SQR_CONTENT_HASH
FieldDescription
METHODUppercase HTTP method — GET, POST, etc.
PATHURL path only, no host, no query string — e.g. /api/integration/v1/devices
SORTED_QUERY_STRINGQuery parameters sorted by key and joined with & — e.g. foo=1&z=2. Empty string if no query params.
UNIX_TIMESTAMP_SECONDSCurrent Unix time in seconds as a decimal integer string.
X_SQR_CONTENT_HASHLowercase hex SHA-256 of the raw request body bytes. For empty/no body: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855

Then compute:

Signature = HMAC-SHA256(api_secret, canonical_string)  →  lowercase hex

Example

# Signing GET /api/integration/v1/devices with no body

canonical = "GET\n/api/integration/v1/devices\n\n1700000000\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"

Authorization: SQRv1 Credential=my-api-key, Timestamp=1700000000, Signature=<hmac_hex>
X-SQR-Content-Hash: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855

Signing functions

Copy the function for your language into your codebase. Each implementation is self-contained and uses only the standard library.

const crypto = require("crypto");

/**
 * Sign a ScanSeqr Integration API request.
 * Returns the two headers that must be added to the request.
 */
function signRequest({ key, secret, method, path, query = "", body = "" }) {
  const timestamp = Math.floor(Date.now() / 1000);

  const contentHash = crypto
    .createHash("sha256")
    .update(body, "utf8")
    .digest("hex");

  const sortedQuery = query
    ? new URLSearchParams(
        [...new URLSearchParams(query).entries()].sort(([a], [b]) => a.localeCompare(b))
      ).toString()
    : "";

  const canonical = [method.toUpperCase(), path, sortedQuery, timestamp, contentHash].join("\n");

  const signature = crypto
    .createHmac("sha256", secret)
    .update(canonical, "utf8")
    .digest("hex");

  return {
    Authorization: `SQRv1 Credential=${key}, Timestamp=${timestamp}, Signature=${signature}`,
    "X-SQR-Content-Hash": contentHash,
  };
}

// Usage — GET request (no body)
const headers = signRequest({
  key: "YOUR_API_KEY",
  secret: "YOUR_API_SECRET",
  method: "GET",
  path: "/api/integration/v1/devices",
});

// Usage — POST request with a JSON body
const body = JSON.stringify({ name: "Key for Bob", groupId: "...", startTime: "...", endTime: "..." });
const postHeaders = signRequest({
  key: "YOUR_API_KEY",
  secret: "YOUR_API_SECRET",
  method: "POST",
  path: "/api/integration/v1/accesskey",
  body,
});
import { createHash, createHmac } from "crypto";

interface SignOptions {
  key: string;
  secret: string;
  method: string;
  path: string;
  query?: string;
  body?: string;
}

interface SignedHeaders {
  Authorization: string;
  "X-SQR-Content-Hash": string;
}

function signRequest({ key, secret, method, path, query = "", body = "" }: SignOptions): SignedHeaders {
  const timestamp = Math.floor(Date.now() / 1000);

  const contentHash = createHash("sha256").update(body, "utf8").digest("hex");

  const sortedQuery = query
    ? new URLSearchParams(
        [...new URLSearchParams(query).entries()].sort(([a], [b]) => a.localeCompare(b))
      ).toString()
    : "";

  const canonical = [method.toUpperCase(), path, sortedQuery, timestamp, contentHash].join("\n");

  const signature = createHmac("sha256", secret).update(canonical, "utf8").digest("hex");

  return {
    Authorization: `SQRv1 Credential=${key}, Timestamp=${timestamp}, Signature=${signature}`,
    "X-SQR-Content-Hash": contentHash,
  };
}

export { signRequest };
import hashlib
import hmac
import time
from urllib.parse import urlencode, parse_qsl


def sign_request(key: str, secret: str, method: str, path: str,
                 query: str = "", body: str = "") -> dict:
    """
    Sign a ScanSeqr Integration API request.
    Returns a dict of headers to add to the request.
    """
    timestamp = int(time.time())

    content_hash = hashlib.sha256(body.encode()).hexdigest()

    sorted_query = urlencode(sorted(parse_qsl(query))) if query else ""

    canonical = "\n".join([
        method.upper(),
        path,
        sorted_query,
        str(timestamp),
        content_hash,
    ])

    signature = hmac.new(secret.encode(), canonical.encode(), hashlib.sha256).hexdigest()

    return {
        "Authorization": f"SQRv1 Credential={key}, Timestamp={timestamp}, Signature={signature}",
        "X-SQR-Content-Hash": content_hash,
    }


# Usage — GET request
import requests

headers = sign_request(
    key="YOUR_API_KEY",
    secret="YOUR_API_SECRET",
    method="GET",
    path="/api/integration/v1/devices",
)
response = requests.get("https://api-uat.scanseqr.com/api/integration/v1/devices", headers=headers)

# Usage — POST with a JSON body
import json

body = json.dumps({"name": "Key for Bob", "groupId": "...", "startTime": "...", "endTime": "..."})
headers = sign_request(
    key="YOUR_API_KEY",
    secret="YOUR_API_SECRET",
    method="POST",
    path="/api/integration/v1/accesskey",
    body=body,
)
headers["Content-Type"] = "application/json"
response = requests.post("https://api-uat.scanseqr.com/api/integration/v1/accesskey",
                         headers=headers, data=body)
<?php

/**
 * Sign a ScanSeqr Integration API request.
 * Returns an array of headers to add to the request.
 */
function sqrv1_sign(string $key, string $secret, string $method, string $path,
                    string $query = '', string $body = ''): array
{
    $timestamp = time();

    $contentHash = hash('sha256', $body);

    $sortedQuery = '';
    if ($query !== '') {
        parse_str($query, $params);
        ksort($params);
        $sortedQuery = http_build_query($params);
    }

    $canonical = implode("\n", [
        strtoupper($method),
        $path,
        $sortedQuery,
        (string) $timestamp,
        $contentHash,
    ]);

    $signature = hash_hmac('sha256', $canonical, $secret);

    return [
        'Authorization'      => "SQRv1 Credential={$key}, Timestamp={$timestamp}, Signature={$signature}",
        'X-SQR-Content-Hash' => $contentHash,
    ];
}

// Usage — GET request
$headers = sqrv1_sign('YOUR_API_KEY', 'YOUR_API_SECRET', 'GET', '/api/integration/v1/devices');

$ch = curl_init('https://api-uat.scanseqr.com/api/integration/v1/devices');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, array_map(
    fn($k, $v) => "$k: $v",
    array_keys($headers), $headers
));
$response = curl_exec($ch);

// Usage — POST with a JSON body
$body = json_encode(['name' => 'Key for Bob', 'groupId' => '...', 'startTime' => '...', 'endTime' => '...']);
$headers = sqrv1_sign('YOUR_API_KEY', 'YOUR_API_SECRET', 'POST', '/api/integration/v1/accesskey', '', $body);
$headers['Content-Type'] = 'application/json';
package main

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"net/url"
	"sort"
	"strings"
	"time"
)

// SQRv1Sign returns the Authorization and X-SQR-Content-Hash header values
// for the given request parameters.
func SQRv1Sign(key, secret, method, path, query, body string) (authorization, contentHash string) {
	timestamp := time.Now().Unix()

	h := sha256.Sum256([]byte(body))
	contentHash = hex.EncodeToString(h[:])

	// Sort query parameters by key.
	sortedQuery := ""
	if query != "" {
		params, _ := url.ParseQuery(query)
		keys := make([]string, 0, len(params))
		for k := range params {
			keys = append(keys, k)
		}
		sort.Strings(keys)
		parts := make([]string, 0, len(keys))
		for _, k := range keys {
			parts = append(parts, k+"="+params.Get(k))
		}
		sortedQuery = strings.Join(parts, "&")
	}

	canonical := strings.Join([]string{
		strings.ToUpper(method),
		path,
		sortedQuery,
		fmt.Sprintf("%d", timestamp),
		contentHash,
	}, "\n")

	mac := hmac.New(sha256.New, []byte(secret))
	mac.Write([]byte(canonical))
	sig := hex.EncodeToString(mac.Sum(nil))

	authorization = fmt.Sprintf("SQRv1 Credential=%s, Timestamp=%d, Signature=%s", key, timestamp, sig)
	return
}

// Usage — GET request
func exampleGet() {
	auth, hash := SQRv1Sign("YOUR_API_KEY", "YOUR_API_SECRET", "GET", "/api/integration/v1/devices", "", "")
	fmt.Println("Authorization:", auth)
	fmt.Println("X-SQR-Content-Hash:", hash)
}
#!/usr/bin/env bash
# sqrv1_sign.sh — requires bash, openssl, date (GNU or macOS)
#
# Usage:
#   source sqrv1_sign.sh
#   sqrv1_sign GET /api/integration/v1/devices
#   sqrv1_sign POST /api/integration/v1/accesskey '{"name":"Bob","groupId":"...","startTime":"...","endTime":"..."}'

SQR_API_KEY="YOUR_API_KEY"
SQR_API_SECRET="YOUR_API_SECRET"
SQR_BASE_URL="https://api-uat.scanseqr.com/api/integration/v1"

sqrv1_sign() {
  local method="${1^^}"   # uppercase
  local path="$2"
  local body="${3:-}"
  local query="${4:-}"

  local timestamp
  timestamp=$(date -u +%s)

  local content_hash
  content_hash=$(printf '%s' "$body" | openssl dgst -sha256 -hex | awk '{print $2}')

  # Sort query params (simple key-sort; extend if values contain & or =)
  local sorted_query=""
  if [ -n "$query" ]; then
    sorted_query=$(echo "$query" | tr '&' '\n' | sort | tr '\n' '&' | sed 's/&$//')
  fi

  local canonical="${method}
${path}
${sorted_query}
${timestamp}
${content_hash}"

  local signature
  signature=$(printf '%s' "$canonical" | openssl dgst -sha256 -hmac "$SQR_API_SECRET" -hex | awk '{print $2}')

  local auth="SQRv1 Credential=${SQR_API_KEY}, Timestamp=${timestamp}, Signature=${signature}"

  if [ -z "$body" ]; then
    curl -s \
      -H "Authorization: $auth" \
      -H "X-SQR-Content-Hash: $content_hash" \
      "${SQR_BASE_URL}${path}"
  else
    curl -s -X "$method" \
      -H "Authorization: $auth" \
      -H "X-SQR-Content-Hash: $content_hash" \
      -H "Content-Type: application/json" \
      -d "$body" \
      "${SQR_BASE_URL}${path}"
  fi
}

# Examples:
# sqrv1_sign GET /devices
# sqrv1_sign POST /accesskey '{"name":"Bob","groupId":"uuid","startTime":"2025-01-01T00:00:00Z","endTime":"2026-01-01T00:00:00Z"}'

Error responses

StatusMeaning
403 (no body)Authentication failed. Check that the credential was created after SQRv1 was introduced, that the timestamp is within 5 minutes of server time, and that your signing implementation matches the algorithm above.
401Authorization header missing or malformed.
Use the Playground to generate a signed curl command and verify your credentials without writing any code.