Skip to main content
Direct signer integration uses:
  1. GET .../signer/routing — fetch the DMZ URL and webhook URL for your app.
  2. @pymthouse/builder-sdk/signer/server — proxy signing requests directly to the remote signer DMZ with JWT minting.
  3. @pymthouse/builder-sdk/signer/webhook — handle go-livepeer identity webhook calls (POST /authorize) to authenticate end-users.

Token lifecycle

The following diagram shows how a signing request flows from the app backend through PymtHouse to the remote signer DMZ:

Fetch signer routing config

GET /api/v1/apps/{clientId}/signer/routing
Authorization: Basic base64(m2m_id:m2m_secret)
Returns the remote DMZ URL, JWKS URL, webhook URL, and metering mode for the app. Response:
{
  "dmzUrl": "https://dmz.example.com",
  "jwksUrl": "https://your-pymthouse.example/api/v1/oidc/jwks",
  "webhookUrl": "https://your-app.example/authorize",
  "meteringMode": "kafka"
}
FieldDescription
dmzUrlThe remote signer DMZ base URL. Forward signing requests here.
jwksUrlPymtHouse JWKS endpoint for the DMZ to validate JWTs.
webhookUrlThe identity webhook URL configured on the go-livepeer DMZ (-remoteSignerWebhookUrl).
meteringMode"kafka" (async Kafka collector) or "direct".
SDK:
const routing = await client.getSignerRouting();
// routing.dmzUrl, routing.webhookUrl

Direct DMZ proxy (@pymthouse/builder-sdk/signer/server)

Use createDirectSignerProxyHandler to build an HTTP handler in your backend that:
  1. Mints a user JWT (or signer session) via the Builder API or OIDC.
  2. Forwards the original signing request to the remote DMZ with the JWT as the Bearer token.
  3. Streams the response back to the caller.
import { createDirectSignerProxyHandler, createSignerTokenManager } from "@pymthouse/builder-sdk/signer/server";
import { createPmtHouseClientFromEnv } from "@pymthouse/builder-sdk/env";

const client = createPmtHouseClientFromEnv();
const routing = await client.getSignerRouting();

const handler = createDirectSignerProxyHandler({
  client,
  dmzUrl: routing.dmzUrl,
  // Optional: cache signer tokens to reduce round-trips
  tokenManager: createSignerTokenManager({ client }),
});

// In your HTTP framework:
export async function POST(request: Request) {
  return handler(request);
}

Low-level helpers

For custom forwarding logic:
import {
  forwardDirectSignerRequest,
  mintUserSignerToken,
} from "@pymthouse/builder-sdk/signer/server";

// Mint a signer JWT for a specific user
const token = await mintUserSignerToken(client, {
  externalUserId: "user-123",
  scope: "sign:job",
});

// Forward to DMZ with the token
const response = await forwardDirectSignerRequest({
  dmzUrl: routing.dmzUrl,
  signerToken: token.access_token,
  request: incomingRequest,
});

Device and API key exchange handlers

For CLI device flows and API key integrations, builder-sdk provides purpose-built handlers:
import {
  createDeviceExchangeHandler,
  createApiKeyExchangeHandler,
} from "@pymthouse/builder-sdk/signer/server";

// POST /api/signer/device/exchange — device token → signer JWT
const deviceExchange = createDeviceExchangeHandler({ client });

// POST /api/signer/api-key/exchange — API key → signer session via facade
const apiKeyExchange = createApiKeyExchangeHandler({
  client,
  facadeUrl: process.env.DASHBOARD_ORIGIN!,
});

Identity webhook (@pymthouse/builder-sdk/signer/webhook)

go-livepeer calls POST /authorize (configured via -remoteSignerWebhookUrl) for every signing request to verify the end-user’s credentials and receive an auth_id for usage attribution.

Setup

import {
  createRemoteSignerAuthorizeHandler,
  createOidcRemoteSignerWebhookConfig,
} from "@pymthouse/builder-sdk/signer/webhook";

const authorize = createRemoteSignerAuthorizeHandler(
  createOidcRemoteSignerWebhookConfig({
    webhookSecret: process.env.WEBHOOK_SECRET!,
    jwtIssuer: process.env.JWT_ISSUER!,
    jwtAudience: process.env.JWT_AUDIENCE!,
    claimMapping: {
      claimClientId: "azp",
      usageSubjectType: "auth0_user_id",
    },
  }),
);

// Mount at your webhook URL:
export async function POST(request: Request) {
  return authorize(request);
}

End-user auth adapters

The webhook handler is split into two layers:
  • Transport auth — validates the go-livepeer shared secret (WEBHOOK_SECRET) that proves the request came from your DMZ, not an arbitrary caller.
  • End-user auth (EndUserAuthVerifier) — validates the end-user’s credential in the signing request and resolves it to a UsageIdentity.
Four built-in adapters are provided:
AdapterImportUse case
OIDC (default)@pymthouse/builder-sdk/signer/webhook/adapters/oidcPymtHouse-issued JWTs or Auth0/OIDC tokens
API key@pymthouse/builder-sdk/signer/webhook/adapters/api-keyResolve pmth_* API keys to a UsageIdentity
Trusted headers@pymthouse/builder-sdk/signer/webhook/adapters/trusted-headersReverse proxy that injects pre-authenticated user id
Composite@pymthouse/builder-sdk/signer/webhook/adapters/compositeFirst-match across multiple verifiers

API key adapter

import { createApiKeyEndUserVerifier } from "@pymthouse/builder-sdk/signer/webhook/adapters/api-key";
import { createRemoteSignerAuthorizeHandler } from "@pymthouse/builder-sdk/signer/webhook";

const handler = createRemoteSignerAuthorizeHandler({
  webhookSecret: process.env.WEBHOOK_SECRET!,
  endUserAuth: {
    kind: "api-key",
    verifier: createApiKeyEndUserVerifier({
      issuer: process.env.JWT_ISSUER!,
      resolveApiKey: async (key) => {
        // Return UsageIdentity or null
        const user = await db.findUserByApiKey(key);
        return user
          ? { identity: { auth_id: user.id, externalUserId: user.externalId }, expiry: null }
          : null;
      },
    }),
  },
});

Custom verifier

Implement the EndUserAuthVerifier interface for any custom auth scheme:
import type { EndUserAuthVerifier } from "@pymthouse/builder-sdk/signer/webhook";

const customVerifier: EndUserAuthVerifier = {
  kind: "custom",
  verify: async ({ authorization, payload, request }) => {
    // Validate credentials, return UsageIdentity
    return {
      identity: { auth_id: "user-abc", externalUserId: "user-123" },
      expiry: Math.trunc(Date.now() / 1000) + 300,
    };
  },
};

Webhook environment variables

VariableDescription
WEBHOOK_SECRETShared secret between go-livepeer DMZ and your webhook handler. Set on the DMZ via -remoteSignerWebhookSecret.
JWT_ISSUEROIDC issuer URL for JWT validation (e.g. https://your-pymthouse.example/api/v1/oidc).
JWT_AUDIENCEExpected aud claim in end-user JWTs.
CLAIM_CLIENT_IDJWT claim to use as the client id (default azp; for Auth0 set to azp).

Security guidance

  • WEBHOOK_SECRET authenticates that the signing request came from your go-livepeer DMZ instance. Rotate it if the DMZ is compromised.
  • The end-user EndUserAuthVerifier authenticates the user making the signing request. Keep the two auth layers separate — webhook secret is transport; end-user auth is identity.
  • Configure the go-livepeer DMZ with -remoteSignerWebhookUrl pointing to your /authorize endpoint and -remoteSignerWebhookSecret matching WEBHOOK_SECRET.
  • The DMZ validates JWTs against the JWKS URL from getSignerRouting() — ensure jwksUrl is reachable from the DMZ host.