SSO Connect

Let users who are already authenticated on your existing website or app sign into the marketplace without creating a separate account. Tradly SSO uses HMAC-SHA256 signed payloads — no SAML metadata or OAuth app registration required.

How it works

  1. 1
    Marketplace calls POST /v1/users/sso with the current domain. Tradly creates a nonce, signs a payload with your shared secret, and returns a redirect_url pointing to your identity endpoint.
  2. 2
    App opens the redirect URL. Your endpoint receives ?identity=tradly&payload=<b64>&sig=<hmac>, verifies the signature, looks up the logged-in user, and builds a return payload.
  3. 3
    Your endpoint signs the return payload (JSON → base64, HMAC-SHA256 with the same secret) and redirects to the return_url from the original payload.
  4. 4
    App calls POST /v1/users/sso_return with the payload and signature. Tradly verifies, validates the nonce (10-minute window), and logs in or registers the user. Returns auth + refresh tokens.

Configuration

Three keys must be set via POST /v1/configs before SSO works.

sso_enabled

Boolean. Must be true — without this both endpoints return ACTION_NOT_ALLOWED.

sso.sso_secret

Shared HMAC secret. Use a cryptographically random string of at least 32 characters. Must match on both sides.

sso.sso_redirect_url

HTTPS URL of your identity endpoint. Tradly appends ?identity=tradly&payload=…&sig=… to this URL.

POST /v1/configs
Content-Type: application/json

{
  "configs": [
    { "key_group": "sso", "key": "sso_enabled",      "value": true,                          "secured": false },
    { "key_group": "sso", "key": "sso_secret",       "value": "your-32-char-random-secret",  "secured": true  },
    { "key_group": "sso", "key": "sso_redirect_url", "value": "https://your-site.com/sso",   "secured": false }
  ]
}

Endpoints

POST /v1/users/sso — initiate

No auth required. Call before the user has a session.

POST /v1/users/sso
tenant_id: <tenant_id>    (header)

{ "domain": "marketplace.your-client.com" }

// Response
{
  "status": true,
  "data": {
    "redirect_url": "https://your-site.com/sso?identity=tradly&payload=<b64>&sig=<hmac>"
  }
}

POST /v1/users/sso_return — complete

No auth required. Call after your identity endpoint redirects back.

POST /v1/users/sso_return
tenant_id: <tenant_id>    (header)

{
  "payload": "<base64-encoded-return-payload>",
  "sig":     "<hmac-sha256-hex>"
}

// Response
{
  "status": true,
  "data": {
    "user": {
      "id": "usr_abc123",
      "first_name": "Jane",
      "last_name": "Doe",
      "email": "jane@example.com",
      "email_verified": true,
      "key": {
        "auth_key": "<jwt>",
        "refresh_key": "<jwt>",
        "firebase_token": "<token>"
      }
    }
  }
}

Return payload fields

Your identity endpoint must send this JSON, signed and base64-encoded.

FieldTypeRequiredDescription
noncestringrequiredEcho back the nonce from the inbound payload. Tradly uses this to match and expire the SSO request.
external_idstringrequiredYour stable unique ID for the user. Becomes the Tradly username. Must never change. UUID or numeric ID works.
emailstringoptionalStored on first registration only.
email_verifiedbooleanoptionalDefaults to false.
first_namestringoptionalStored on registration.
last_namestringoptionalStored on registration.

Signing the return payload

Sign the raw JSON string — not the base64-encoded version. Tradly decodes base64 first, then verifies HMAC against the decoded string.
// Node.js example
const payloadStr = JSON.stringify({
  nonce:          inbound.nonce,
  external_id:    String(user.id),
  email:          user.email,
  email_verified: user.emailVerified,
  first_name:     user.firstName,
  last_name:      user.lastName,
})

const sig     = crypto.createHmac('sha256', SSO_SECRET).update(payloadStr).digest('hex')
const encoded = Buffer.from(payloadStr).toString('base64')

res.redirect(`${inbound.return_url}?payload=${encoded}&sig=${sig}`)

Errors

HTTPCodeCause
412ACTION_NOT_ALLOWEDsso_enabled is false or missing.
412108HMAC signature mismatch — wrong secret or payload was modified.
412109Nonce not found, already used, 10-minute window expired, or external_id missing.