npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@ascflow/partner-sdk

v1.0.9

Published

Official SDK for partners integrating with ASCFlow's iframe SSO. Generates short-lived embed URLs with HMAC-signed requests.

Readme

@ascflow/partner-sdk

Official Node.js SDK for partners integrating with ASCFlow's iframe SSO. Generates short-lived, single-use embed URLs that you render inside an <iframe> so your end users land in ASCFlow already authenticated — without ever logging in twice.

npm version Node.js License


Table of Contents


What this SDK does

ASCFlow exposes a single endpoint for partner SSO:

POST /partner/auth/embed-url

Calling it requires:

  1. An HMAC-SHA256 signature over a canonical string built from the request
  2. An API key identifying the partner
  3. An Origin registered with ASCFlow for that partner
  4. A short-lived idempotency key to safely retry on network failure

This SDK does all of that for you and returns a one-shot embedUrl that you drop into an <iframe>. It runs on your server (Node.js), never the browser — your secret must never leave the backend.

                    ┌──────────────────────────────────────────┐
                    │ Your partner backend (Node.js)           │
                    │                                          │
  end user ─── HTTP ─►  ascflow-partner-sdk.createEmbedUrl()   │
                    │           │                              │
                    │           ▼  HMAC-signed POST            │
                    │  ┌──────────────────────────────┐        │
                    │  │  ASCFlow gateway              │        │
                    │  │  POST /partner/auth/embed-url │        │
                    │  └──────────────────────────────┘        │
                    │           │                              │
                    │           ▼                              │
                    │     { embedUrl, expiresIn }              │
                    │                                          │
                    │  Render <iframe src={embedUrl} />        │
                    └──────────────────────────────────────────┘

Don't want to use the SDK? Partners on other stacks (PHP, Python, Go, Ruby, Java...) can implement the same protocol by hand following docs/INTEGRACAO_SEM_SDK.pt-BR.md (Portuguese, with copy-pasteable examples in 7 languages and a fixed test vector).

Installation

npm install @ascflow/partner-sdk
# or
pnpm add @ascflow/partner-sdk
# or
yarn add @ascflow/partner-sdk

Requirements

  • Node.js 18 or later (uses the global fetch)
  • TypeScript 5.x is supported but not required

Quick start

import { AscflowPartnerClient } from '@ascflow/partner-sdk';

// Reads ASCFLOW_PARTNER_* environment variables automatically.
const ascflow = new AscflowPartnerClient();

const { embedUrl, expiresIn } = await ascflow.createEmbedUrl({
  email: '[email protected]',
  name: 'Jane Doe',
  cpf: '13166917731',
  role: 'user',
  redirectPath: '/flow/onboarding',
});

console.log(embedUrl);   // https://app.ascflow.com/embed?code=...&r=%2Fflow%2Fonboarding
console.log(expiresIn);  // 90  (seconds)

A complete Express handler:

import express from 'express';
import { AscflowPartnerClient, AscflowError } from '@ascflow/partner-sdk';

const app = express();
const ascflow = new AscflowPartnerClient();

app.get('/render-ascflow', async (req, res) => {
  // Authenticate and authorize the request on YOUR side first.
  const user = await getCurrentUser(req);
  if (!user) return res.status(401).end();

  try {
    const { embedUrl, expiresIn } = await ascflow.createEmbedUrl({
      email: user.email,
      name: user.fullName,
      cpf: user.cpf,
      role: user.role,
      redirectPath: typeof req.query.r === 'string' ? req.query.r : '/',
    });
    res.json({ embedUrl, expiresIn });
  } catch (err) {
    if (err instanceof AscflowError) {
      return res.status(502).json({ error: err.code, message: err.message });
    }
    throw err;
  }
});

Configuration

The client reads its configuration from environment variables by default. You can override any field by passing it to the constructor.

Environment variables

| Variable | Required | Description | |---|:---:|---| | ASCFLOW_PARTNER_API_KEY | ✅ | Your public partner identifier. | | ASCFLOW_PARTNER_SECRET | ✅ | The signing secret. Server-side only. | | ASCFLOW_PARTNER_ORIGIN | ✅ | Origin sent on every request. Must match an origin registered for your partner. | | ASCFLOW_PARTNER_BASE_URL | ✅ | Gateway base URL (e.g. https://gateway.ascflow.com). | | ASCFLOW_PARTNER_TIMEOUT_MS | – | Request timeout in ms. Default 10000. | | ASCFLOW_PARTNER_MAX_RETRIES | – | Max automatic retries on 5xx / network errors. Default 2. |

A copy-pasteable .env.example ships with the package.

Constructor overrides

const ascflow = new AscflowPartnerClient({
  apiKey: process.env.MY_KEY,
  secret: process.env.MY_SECRET,
  origin: 'https://app.partner.com',
  baseUrl: 'https://gateway.ascflow.com',
  timeoutMs: 5_000,
  maxRetries: 3,

  // Advanced — supply your own fetch (e.g. with a proxy agent or for tests).
  fetch: customFetch,

  // Default 'seconds'. Switch to 'milliseconds' only if your gateway expects it.
  timestampFormat: 'seconds',
});

⚠️ Timestamp format — the canonical string contains the timestamp, so the SDK and the gateway must agree on its format. The default 'seconds' matches the reference Postman pre-request script (Math.floor(Date.now() / 1000)). If your gateway validates milliseconds instead, set timestampFormat: 'milliseconds'.


Embedding the iframe

Once you have an embedUrl, render it on the page where the partner wants ASCFlow to appear:

<iframe
  src="https://app.ascflow.com/embed?code=a3f8...&r=%2Fflow%2Fmeu-processo"
  width="100%"
  height="800"
  frameborder="0"
  sandbox="allow-scripts allow-same-origin allow-forms allow-popups"
  allow="clipboard-write"
></iframe>

The sandbox attribute is your defense-in-depth control. Keep it as restrictive as your use case allows. ASCFlow already enforces strict Content-Security-Policy and frame-ancestors on its side.


API reference

new AscflowPartnerClient(config?)

Creates a client. With no argument, it reads everything from environment variables. See Configuration.

client.createEmbedUrl(input)

Generates a one-shot embed URL.

Input (CreateEmbedUrlInput)

| Field | Type | Required | Notes | |---|---|:---:|---| | email | string | ✅ | The end user's email. | | name | string | ✅ | Display name shown inside ASCFlow. | | cpf | string | ✅ | End user's CPF (Brazilian tax ID). Digits only, exactly 11 characters. | | role | 'admin' \| 'user' | ✅ | Role of the end user inside ASCFlow. | | redirectPath | string | – | Path inside ASCFlow to land on. Must start with / and must not start with //. Default '/'. | | idempotencyKey | string | – | Custom idempotency key. Default: a random UUID. |

Returns (Promise<CreateEmbedUrlResponse>)

{
  embedUrl: string;   // URL to put in <iframe src>
  expiresIn: number;  // seconds until the URL is invalid (typically 60–120)
}

Throws — see Error handling.

client.signedRequest({ method, path, body, idempotencyKey })

Lower-level helper for advanced use cases. Signs and sends an arbitrary request to the gateway with the same canonicalization rules. Most users do not need this.

Helpers (named exports)

For testing and tooling integrations, the SDK also exports:

import {
  buildCanonicalString,
  hmacSha256Hex,
  sha256Hex,
  normalizePath,
  currentTimestamp,
} from '@ascflow/partner-sdk';

These let you reproduce the exact wire format the SDK uses (useful when debugging signature mismatches, writing custom transports, or signing requests from a non-Node environment that does its own HTTP).


Error handling

Every SDK error extends AscflowError, which has these properties:

| Property | Type | Notes | |---|---|---| | code | string | Stable machine-readable code (e.g. AUTH_ERROR). | | status | number? | HTTP status, when applicable. | | requestId | string? | From the x-request-id response header, when present. | | details | unknown | Parsed response body, for forensic logging. |

Subclasses you can instanceof-narrow:

| Class | When | |---|---| | AscflowConfigError | Missing or invalid SDK configuration. No request was sent. | | AscflowValidationError | Local input validation failed (bad email, bad redirectPath, oversized body). No request was sent. | | AscflowAuthError | HTTP 401 / 403. Signature, timestamp, origin, or partner status problem. | | AscflowRateLimitError | HTTP 429. Has retryAfterSeconds parsed from Retry-After. | | AscflowRequestError | Other 4xx. Usually means a request-shape problem. | | AscflowServerError | HTTP 5xx after retries are exhausted. | | AscflowNetworkError | Connection error or timeout after retries are exhausted. |

import {
  AscflowAuthError,
  AscflowRateLimitError,
  AscflowError,
} from '@ascflow/partner-sdk';

try {
  await ascflow.createEmbedUrl({ email, name });
} catch (err) {
  if (err instanceof AscflowAuthError) {
    logger.error({ requestId: err.requestId }, 'ASCFlow rejected our credentials');
    return res.status(502).json({ error: 'sso_auth_failed' });
  }
  if (err instanceof AscflowRateLimitError) {
    res.setHeader('Retry-After', String(err.retryAfterSeconds ?? 30));
    return res.status(429).end();
  }
  if (err instanceof AscflowError) {
    logger.error({ code: err.code, status: err.status }, err.message);
    return res.status(502).json({ error: 'sso_unavailable' });
  }
  throw err;
}

Retries and idempotency

The SDK retries automatically on:

  • Network errors (DNS, connection refused, timeout)
  • HTTP 408, 425, 429, 500, 502, 503, 504

Retries use exponential backoff with jitter and respect the Retry-After header on 429. The default is 2 retries (maxRetries: 2); set to 0 to disable.

Every retry sends the same Idempotency-Key, so the gateway can dedupe duplicates. If you don't supply one, the SDK generates a fresh UUID per createEmbedUrl call. Supply your own only if you need request-level idempotency across process restarts (e.g. you're queuing the call and might recover-and-retry minutes later).


How requests are signed

The SDK builds and signs every request like this:

canonicalString = METHOD + "\n"
                + PATH + "\n"
                + TIMESTAMP + "\n"
                + SHA256_HEX(BODY)

X-Signature = HMAC_SHA256_HEX(secret, canonicalString)

Headers sent:

Content-Type:    application/json
Origin:          <ASCFLOW_PARTNER_ORIGIN>
X-Partner-Key:   <ASCFLOW_PARTNER_API_KEY>
X-Timestamp:     <unix seconds>
X-Signature:     <hex HMAC>
Idempotency-Key: <UUID, auto or caller-supplied>

PATH is normalized: leading slash, no trailing slash (except for /), no empty segments. BODY is hashed as the exact UTF-8 bytes of the serialized JSON, so the SDK serializes it once and uses the same string for both the hash and the request body. There is no "canonical JSON"; the bytes that are hashed are the bytes that are sent.

If you ever need to verify a signature manually:

import { buildCanonicalString, hmacSha256Hex } from '@ascflow/partner-sdk';

const canonical = buildCanonicalString({
  method: 'POST',
  path: '/partner/auth/embed-url',
  timestamp: '1730000000',
  body: '{"email":"[email protected]","name":"A","redirectPath":"/","cpf":"00000000000","role":"user"}',
});
const signature = hmacSha256Hex(secret, canonical);

Security

In short: never expose your secret to the browser, never commit it, and rotate it through the ASCFlow admin panel. The SDK enforces good defaults but cannot stop you from leaking a secret you've copied somewhere unsafe.

For a deeper checklist — including handling redirectPath, idempotency, secret rotation, and what to log — see SECURITY.md.


Troubleshooting

AscflowAuthError: invalid signature

In order of likelihood:

  1. Wrong secret. Check ASCFLOW_PARTNER_SECRET. The secret is shown only once when generated; if you lost it, rotate to a new one.
  2. Timestamp format mismatch. Default is seconds. If your gateway is configured for milliseconds, set timestampFormat: 'milliseconds'.
  3. Body was modified after signing. This shouldn't happen with the SDK alone, but it can if a proxy, middleware, or custom fetch rewrites the body. The hash is computed over the exact bytes that go on the wire.
  4. Path mismatch. If you put a custom baseUrl with a path prefix (e.g. https://gateway.ascflow.com/v1), make sure your gateway is configured to canonicalize the same way.

AscflowAuthError: origin not allowed

The Origin you sent is not on your partner's allowlist. Compare:

console.log(process.env.ASCFLOW_PARTNER_ORIGIN);

against the value registered with ASCFlow. The match is exact — trailing slashes, scheme, and port all matter.

AscflowAuthError: timestamp expired

Your server clock is more than the allowed skew off real time (typically 60s). Run NTP on the host. The SDK uses the local clock; it has no way to compensate.

AscflowValidationError: redirectPath must not start with "//" ...

You're trying to pass an absolute URL or a protocol-relative path to redirectPath. This is blocked locally as defense-in-depth — the gateway would reject it anyway. Use a path beginning with a single /.

Requests work in Postman but fail with the SDK

Almost always one of:

  • The Postman script uses seconds; your gateway is configured for milliseconds, or vice-versa. Compare with the SDK's timestampFormat.
  • The Postman body has different whitespace / key order than what JSON.stringify produces. The SDK hashes the bytes it actually sends, so this is fine on the SDK side — the issue would be on the gateway if it validates against a re-serialized body. Confirm the gateway hashes req.rawBody, not JSON.stringify(req.body).

TypeScript

Types ship with the package; no need to install @types/@ascflow/partner-sdk. Both ESM (import) and CJS (require) consumers are supported.

import type {
  AscflowPartnerClientConfig,
  CreateEmbedUrlInput,
  CreateEmbedUrlResponse,
} from '@ascflow/partner-sdk';

Versioning and support

This SDK follows Semantic Versioning. The current major version is 0.x, which means the API may change in minor releases until 1.0.0. Pin to an exact version in production until then.

  • Bug reports / questions: open an issue on GitHub.
  • Security issues: see SECURITY.md for the disclosure process. Do not open a public issue for vulnerabilities.

License

MIT © ASCFlow