Server integration

Server integration

Add phone binding and confirmation to your website with API parameter references and official SDK examples.

The AuthLink production API is https://api.getauthlink.com. Use your own API for the application backend; the AuthLink website domain is not an API endpoint.

Integration flow and trust boundaries

Use the official SDK to integrate phone confirmation. Keep integration credentials on your website backend and follow the steps below to create requests and handle results.
  1. The operator creates a separate tenant for your website and delivers its credentials to your backend.
  2. The backend obtains a stable user ID from the signed-in session and generates random state, a creation key and a separate consumption key.
  3. Create a bind / confirm request, save the user, session, purpose, state, requestId, expiresAt and keys, then return only the public ticket to the browser.
  4. The user checks the website, account and code in AuthLink, then approves the request.
  5. On approved / consumed, the backend calls consume using the saved state and consumption key.
  6. Validate the receipt and persist a unique requestId / verificationId record in your own database transaction before returning confirmed. Protected business endpoints check that record.

There is no webhook.approved, six-digit codes and frontend callbacks cannot directly authorize business actions. AuthLink does not receive action, amounts, transactionHash or permission scopes.

Tenants and authentication

Obtain your site’s tenantId, apiKey, origin and expiresAt from the operator. New keys never expire; the operator can still rotate or revoke keys or disable the tenant. Existing keys keep their original expiry. Store keys securely on your backend only.

Header Requirement
Authorization Bearer <tenant API key>; required for all integration endpoints
Content-Type POST uses application/json; unknown fields and duplicate JSON keys are rejected
Idempotency-Key Required for creation and consumption: 16–128 letters, digits, underscores or hyphens. Prefer 32 random bytes in Base64URL and reuse the key on retries.
Origin / Cookie The API rejects browser Origin and Cookie headers; browsers connect through their own website backend.

Create requests and parameters

POST /v1/integration/requests · API origin:


{
  "externalUserId": "stable-user-id",
  "accountLabel": "Account display name",
  "purpose": "bind",
  "state": "REPLACE_WITH_SAVED_43_CHARACTER_RANDOM_STATE"
}
Field Type / required Description
externalUserId string / required 1–128 printable ASCII bytes (0x21–0x7e). Take it from the signed-in session; never recycle it or let the browser choose it.
accountLabel string / required Nonempty, up to 160 UTF-8 bytes; control characters and specified bidi control characters are rejected.
purpose string / required First binding: bind; confirmation after binding: confirm
state string / required 32 secure random bytes encoded as unpadded Base64URL, 43 characters. Save it on the backend and compare it unchanged at consumption.

Success returns HTTP 200 and a ticket:

Response field Meaning
requestId 43-character request identifier
purpose / expiresAt Request purpose and UTC ISO expiry time; lifetime is 120 seconds
qr QR JSON string: v, origin, requestId, token. Deliver only to the frontend component or app; never log it.
qrSvg Compatibility field; the official web package generates its own QR and does not insert this field through innerHTML
pairingCode Current six-digit string; preserve leading zeros
displayCode value、serverTime、validUntil、periodSeconds:30

A new request for the same account rejects its unfinished requests. Code refreshes do not extend expiry or require a new request.

Status, consumption and account APIs

Method / path Input Response / notes
GET /v1/integration/requests/{id} Tenant bearer requestId, status, expiresAt, optional displayCode. Status: new / pending / approved / consumed / rejected / expired
POST /v1/integration/requests/{id}/consume Tenant bearer, Idempotency-Key; body {"state":"saved-43-character-state"} Consume after approval to receive confirmed. Reuse the saved consumption key when the network outcome is uncertain.
POST /v1/integration/accounts/status Tenant bearer; body {"externalUserId":"..."} externalUserId, status:unbound/bound/deleted, platform (null when unbound)
POST /v1/integration/accounts/delete Tenant bearer; body {"externalUserId":"..."} {"status":"deleted"}; permanently revokes the AuthLink binding for this site, with no rebinding. Does not delete the CMS user.
Consumption receipt field Validation
status / authenticationPolicy status must be confirmed; the official SDK validates authenticationPolicy
requestId / tenantId / externalUserId Must match the saved request, tenant, signed-in user and session
purpose / state / expiresAt Must exactly match saved backend values and remain unexpired
verificationId / platform / consumedAt Validate the result ID, android / ios and timestamps; persist with a unique constraint
Deletion permanently revokes only the AuthLink binding. Do not automatically delete CMS users or skip required verification after revocation.

Go SDK and methods

Independent module authlink.local/sdk, Go 1.22+, standard library only. For Go website backends, without importing the AuthLink server or MySQL driver.

Read installation steps, parameters, error handling and complete examples on this page. Copy code directly.

Download Go SDK source · Read the complete example →

Install and initialize

Keep the SDK’s own go.mod after extracting it. Add only the SDK module to your website project.

The SDK ships as source and has no public Go module release yet. Extract it in your website project and use a local replace directive:


mkdir -p third_party
tar -xzf authlink-go.tar.gz -C third_party
go mod edit -replace=authlink.local/sdk=./third_party/authlink-go
go mod edit -require=authlink.local/[email protected]
go mod tidy

import authlink "authlink.local/sdk"

client, err := authlink.New(authlink.Config{
    Origin:   "https://api.getauthlink.com", // API origin, not the marketing site
    TenantID: os.Getenv("AUTHLINK_TENANT_ID"),
    APIKey:   os.Getenv("AUTHLINK_API_KEY"),
})
if err != nil { return err }

// Get userID and label from your authenticated website session.
state, err := authlink.RandomToken()
if err != nil { return err }
createKey, err := authlink.RandomToken()
if err != nil { return err }
consumeKey, err := authlink.RandomToken()
if err != nil { return err }
// Persist state, both keys and user/session first. Reuse them on retries.
ticket, err := client.CreateRequest(ctx, authlink.CreateInput{
    ExternalUserID: userID, AccountLabel: label,
    Purpose: "bind", State: state,
}, createKey)
if err != nil { return err }
expected := authlink.Expected{
    RequestID: ticket.RequestID, ExternalUserID: userID,
    Purpose: ticket.Purpose, State: state, ExpiresAt: ticket.ExpiresAt,
}
// Save expected and consumeKey before returning the public ticket.

// Load saved expected / consumeKey after checking session, CSRF and ownership.
status, err := client.Status(ctx, expected.RequestID)
if err != nil { return err }
if status.Status == "approved" || status.Status == "consumed" {
    result, err := client.Consume(ctx, expected, consumeKey)
    if err != nil { return err }
    // The SDK has validated the full receipt. Persist a unique confirmation
    // in your own transaction, associated with this user/session and business.
    _ = result
}
Config optionsDescription
Origin / TenantID / APIKeyRequired: API HTTPS origin without a trailing slash, tenant ID and private backend key
AllowLocalHTTPDefaults to false; only loopback / localhost development is allowed
HTTPClientOptional *http.Client. The SDK copies it, rejects redirects and disables CookieJar. Use a trusted transport. Each request is capped at 10 seconds; an earlier context deadline or shorter client timeout takes precedence.
MethodReturn value and purpose
CreateRequest(ctx, CreateInput, key)(Ticket, error). CreateInput: ExternalUserID, AccountLabel, Purpose, State. Purpose defaults to confirm; use bind for first binding.
Status(ctx, requestID)(RequestStatus, error). Reads status and current displayCode; does not authorize business actions.
Consume(ctx, Expected, key)(Confirmation, error). Consumes and verifies the policy, tenant, user, request, purpose, state, expiry, result ID and platform.
WaitForConfirmation(ctx, Expected, key, interval)(Confirmation, error). Optional backend polling; consumes approved / consumed results. interval=0 defaults to 1500ms; allowed range is 100ms–10s.
AccountStatus(ctx, externalUserID)(AccountStatus, error). Reads unbound / bound / deleted; Platform is a nullable *string.
DeleteAccount(ctx, externalUserID)error. Permanently revokes this site's AuthLink binding, preventing rebinding; does not delete the CMS user.
RandomToken()(string, error). 32 secure random bytes in Base64URL. Generate and save separate values for state, creation and consumption.
ValidateConfirmation(result, expected, tenantID, now)error. Field validation only, not signature verification. Never trust a browser-supplied receipt as authorization.

Expected contains RequestID, ExternalUserID, Purpose, State and ExpiresAt. Load it from the saved backend request and preserve the exact expiresAt string. Response structs have protocol JSON tags for the official web components. Unknown fields, duplicate keys and case aliases are rejected; update the SDK when protocol fields change.

Waiting, cancellation and errors


result, err := client.WaitForConfirmation(ctx, expected, consumeKey, 0)
switch {
case err == nil:
    // Persist result in your own transaction before updating UI or authorizing work.
    _ = result
case errors.Is(err, authlink.ErrRejected):
    // The user rejected the request.
case errors.Is(err, authlink.ErrExpired):
    // The request expired.
case errors.Is(err, context.Canceled), errors.Is(err, context.DeadlineExceeded):
    // This call was canceled or timed out; the platform request is not revoked.
default:
    var api *authlink.APIError
    if errors.As(err, &api) {
        // Handle api.StatusCode / api.Code; retry with the same consumption key.
    }
}
No automatic network retries. Reuse saved keys when a network outcome is uncertain. Business actions require a consumed, validated result persisted by your backend. Browser polling endpoints usually use Status + Consume; WaitForConfirmation suits longer backend waits.

Complete Go example

The example has two stages: create creates a request; result queries and consumes the saved request. Run result only after phone approval. The example does not implement website sessions or database persistence and does not authorize business actions.

Expand or collapse the complete Go example
// This example creates a request, then queries/consumes a previously saved one.
// Supply tenant credentials only through your backend's private environment.
package main

import (
	"context"
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"os"
	"time"

	authlink "authlink.local/sdk"
)

func run() error {
	mode := flag.String("mode", "create", "create or result")
	user := flag.String("user", "", "stable user ID from your authenticated session")
	label := flag.String("label", "", "account display label")
	purpose := flag.String("purpose", "bind", "bind or confirm")
	flag.Parse()
	client, err := authlink.New(authlink.Config{Origin: os.Getenv("AUTHLINK_ORIGIN"), TenantID: os.Getenv("AUTHLINK_TENANT_ID"), APIKey: os.Getenv("AUTHLINK_API_KEY")})
	if err != nil {
		return err
	}
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()
	switch *mode {
	case "create":
		// For this standalone example, supply already-generated and saved values
		// through the environment. A website must persist them before networking.
		ticket, err := client.CreateRequest(ctx, authlink.CreateInput{ExternalUserID: *user, AccountLabel: *label, Purpose: *purpose, State: os.Getenv("AUTHLINK_STATE")}, os.Getenv("AUTHLINK_CREATE_KEY"))
		if err != nil {
			return err
		}
		// Contains QR token: deliver to the intended user's UI; never log it.
		return json.NewEncoder(os.Stdout).Encode(ticket)
	case "result":
		expected := authlink.Expected{RequestID: os.Getenv("AUTHLINK_REQUEST_ID"), ExternalUserID: *user, Purpose: *purpose, State: os.Getenv("AUTHLINK_STATE"), ExpiresAt: os.Getenv("AUTHLINK_EXPIRES_AT")}
		status, err := client.Status(ctx, expected.RequestID)
		if err != nil {
			return err
		}
		if status.Status != "approved" && status.Status != "consumed" {
			return json.NewEncoder(os.Stdout).Encode(status)
		}
		result, err := client.Consume(ctx, expected, os.Getenv("AUTHLINK_CONSUME_KEY"))
		if err != nil {
			return err
		}
		// In a website, persist result atomically with unique request/result IDs,
		// user/session and business association before returning confirmed.
		_ = result
		fmt.Fprintln(os.Stderr, "Receipt validated; this example does not persist or authorize business.")
		return nil
	default:
		return errors.New("mode must be create or result")
	}
}

func main() {
	if err := run(); err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
}

Command-line options

ParameterPurpose
-modecreate creates a request; result queries and consumes it. Default: create.
-userThe signed-in website user’s stable ID, obtained from the backend session.
-labelThe account label displayed when creating a request.
-purposeUse bind for initial binding and confirm for later confirmations. This example defaults to bind.

Backend environment variables

VariablePurpose
AUTHLINK_ORIGIN / AUTHLINK_TENANT_ID / AUTHLINK_API_KEYThe API origin, tenant ID and private key supplied by the operator. Keep them on the website backend.
AUTHLINK_STATE / AUTHLINK_CREATE_KEY / AUTHLINK_CONSUME_KEYGenerate and persist separate state, creation and consumption keys on your backend. Reuse them on retries.
AUTHLINK_REQUEST_ID / AUTHLINK_EXPIRES_ATFor result, load the request ID and original expiry from the ticket saved on your backend.

The SDK archive includes this example at examples/quickstart/main.go. You can also copy the code above into main.go. Supply credentials through the backend environment, never in source code or the browser.

# Run from a Go project that already includes the SDK module.
# Supply the private environment variables listed above before running.
export AUTHLINK_ORIGIN=https://api.getauthlink.com
go run . -mode create -user stable-user-id -label "Example account" -purpose bind

# After phone approval, load the SAME saved request context before running:
go run . -mode result -user stable-user-id -purpose bind

Node SDK and methods


// Copy /sdk/node/ and /sdk/browser/ with their relative paths. Server import only.
import { AuthLink, randomToken } from './sdk/node/authlink.mjs';
const auth = new AuthLink({
  origin: 'https://api.getauthlink.com',
  tenantId: process.env.AUTHLINK_TENANT_ID,
  apiKey: process.env.AUTHLINK_API_KEY,
});
// user must come from your authenticated session. This is the creation stage.
const state = randomToken();
const createKey = randomToken();
const consumeKey = randomToken();
// Persist these values and user/session before networking. Reuse on retries.
const ticket = await auth.createRequest({
  externalUserId: user.id, accountLabel: user.displayName,
  purpose: 'bind', state,
}, createKey);
const expected = { requestId: ticket.requestId, expiresAt: ticket.expiresAt,
  externalUserId: user.id, purpose: 'bind', state };
// Save expected, consumeKey and session association before returning ticket.

// Your result endpoint loads the saved request context:
const status = await auth.status(expected.requestId);
if (['approved', 'consumed'].includes(status.status)) {
  const verified = await auth.consume(expected, consumeKey);
  // The SDK has verified the receipt. Persist a unique confirmation atomically.
  // Return verified to the browser only after the database commit succeeds.
}
Constructor option / method Description
origin / tenantId / apiKey Required: API HTTPS origin without a trailing slash, tenant ID and private key
allowLocalHttp Defaults to false; loopback development only
createRequest(input, key) Create a request; see fields above. Purpose defaults to confirm.
status(requestId, signal?) Read status; signal is an optional AbortSignal
consume(expected, key, signal?) Consume and validate the receipt, returning a verified result
waitForConfirmation(expected, options) Backend polling helper. options.idempotencyKey and onConfirmed are required; optional onRejected / onExpired, signal and intervalMs (default 1500, range 100–10000ms). Your application must still persist the result.
accountStatus(id) / deleteAccount(id) Read / permanently revoke the tenant's account binding

Installation, method options and usage examples are on this page. The Node SDK and its dependency are in one archive. Preserve the relative node and browser directories after extraction; install web components through npm.

Download the Node SDK archive

mkdir -p sdk
tar -xzf authlink-node.tar.gz -C sdk
# Import ./sdk/node/authlink.mjs from your backend project.

Error handling

HTTP Common code Action
400 INVALID_REQUEST / INVALID_SUBJECT / INVALID_DISPLAY_TEXT / INVALID_IDEMPOTENCY_KEY Correct the input constraints. Do not create new requests to hide an uncertain outcome.
401 UNAUTHORIZED / STATE_MISMATCH / SERVER_CREDENTIAL_REQUIRED Check private credentials, state, Host and whether the website backend was bypassed.
404 NOT_FOUND Request or release channel does not exist
409 APPROVAL_REQUIRED / REQUEST_EXPIRED / REQUEST_USED / IDEMPOTENCY_CONFLICT / ACCOUNT_ALREADY_BOUND / ACCOUNT_NOT_BOUND Handle the business state; ask the user to retry after expiry and preserve idempotent recovery.
429 RATE_LIMITED / BUSY Back off and retry with the original idempotency key
503 ATTESTATION_UNAVAILABLE The verification service is temporarily unavailable. Try again later.

Error body: {"code":"..."}. Never treat unknown errors as success. Do not share-cache API responses or log bearer keys, QR tokens, complete proofs or raw secrets.