Help Center

SSO Integration

Embed Issuely into your own app so users sign in seamlessly with a single click.

How it works

Issuely uses JWT-based SSO. Your server generates a short-lived signed token containing the user's details and redirects them to https://issuely.in/api/auth/sso?token=…. Issuely verifies the token, creates the user account if needed, and issues a session — all in one redirect.

  1. User clicks "Open Support" (or similar) in your app.
  2. Your server signs a JWT with your company's SSO secret and redirects the user.
  3. Issuely verifies the signature, provisions the account (first visit only), and logs them in.
  4. The user lands on their Issuely dashboard with no password required.

Setting up SSO

  1. In Issuely go to Settings → SSO and generate your SSO Secret. Keep this secret — anyone who has it can generate valid login tokens.
  2. Note your Company ID (shown on the same page).
  3. In your application, sign a JWT using the secret (HS256) with the payload described below.
  4. Redirect the user to https://issuely.in/api/auth/sso?token=YOUR_TOKEN.

JWT payload

{
  "companyId": "your-company-id",   // required
  "email": "user@example.com",       // required
  "name": "Jane Smith",              // optional — used on first sign-in
  "role": "CUSTOMER",                // optional — CUSTOMER (default) or AGENT
  "projectId": "proj_abc123",        // optional — auto-add to this project
  "iat": 1700000000,                 // issued-at (Unix seconds)
  "exp": 1700000600                  // must expire within 10 minutes of iat
}

Code examples

Node.js

const jwt = require('jsonwebtoken');

const token = jwt.sign(
  {
    companyId: 'your-company-id',
    email: req.user.email,
    name: req.user.name,
    role: 'CUSTOMER',
  },
  process.env.ISSUELY_SSO_SECRET,
  { algorithm: 'HS256', expiresIn: '5m' }
);

res.redirect(`https://issuely.in/api/auth/sso?token=${token}`);

PHP

<?php
use Firebase\JWT\JWT;

$payload = [
    'companyId' => 'your-company-id',
    'email'     => $user->email,
    'name'      => $user->name,
    'role'      => 'CUSTOMER',
    'iat'       => time(),
    'exp'       => time() + 300,
];

$token = JWT::encode($payload, $_ENV['ISSUELY_SSO_SECRET'], 'HS256');
header('Location: https://issuely.in/api/auth/sso?token=' . $token);

Python

import jwt, time, os
from flask import redirect

payload = {
    'companyId': 'your-company-id',
    'email': current_user.email,
    'name': current_user.name,
    'role': 'CUSTOMER',
    'iat': int(time.time()),
    'exp': int(time.time()) + 300,
}

token = jwt.encode(payload, os.environ['ISSUELY_SSO_SECRET'], algorithm='HS256')
return redirect(f'https://issuely.in/api/auth/sso?token={token}')

.NET (C#)

using System.IdentityModel.Tokens.Jwt;
using Microsoft.IdentityModel.Tokens;
using System.Security.Claims;

var key = new SymmetricSecurityKey(
    Encoding.UTF8.GetBytes(Environment.GetEnvironmentVariable("ISSUELY_SSO_SECRET")));

var token = new JwtSecurityToken(
    claims: new[] {
        new Claim("companyId", "your-company-id"),
        new Claim("email", user.Email),
        new Claim("name", user.Name),
        new Claim("role", "CUSTOMER"),
    },
    expires: DateTime.UtcNow.AddMinutes(5),
    signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256)
);

var tokenString = new JwtSecurityTokenHandler().WriteToken(token);
return Redirect($"https://issuely.in/api/auth/sso?token={tokenString}");

Security notes

  • Tokens must expire within 10 minutes of their iat. Longer-lived tokens are rejected.
  • Never expose your SSO secret in client-side code. Always sign on the server.
  • If your secret is compromised, regenerate it immediately in Settings → SSO.
  • Each user's email must be unique across Issuely. An email already registered under a different company will be denied.