Appearance
Are you an LLM? You can read better optimized documentation at /partners/cards/kyc-share-token.md for this page in Markdown format
Verify with your Sumsub workspace
Use this path when you already verify cardholders in your own Sumsub workspace, including the selfie and liveness check. You send Agio a Sumsub share token with the application. The cardholder does not repeat the checks that your workspace already completed.
If you do not use Sumsub, or you cannot collect a selfie, use a hosted link instead.
Before you start
- Ask Agio onboarding to register your Sumsub workspace. Without registration, applications return
KYC_WORKSPACE_NOT_AUTHORIZED. - Get Agio's Sumsub client ID from Agio onboarding. Your share tokens name Agio as the recipient with this ID.
- Give your Sumsub app token a role with the Share applicants data permission. Without it, Sumsub refuses to create share tokens.
- Use a Sumsub sandbox app token with Agio development credentials, and a production app token with production credentials only.
How it works
- Provision the cardholder with
createPartnerCardUser. Agio still forwards this identity with the share token. - Create a Sumsub share token for the cardholder's applicant in your workspace, with Agio's client ID as
forClientId. - Send the token as
partnerKycShareTokenincreateCardApplicationForPartnerUser. - Read
applicationStatus. If the response also holdsapplicationCompletionUrl, the card issuer needs more from the cardholder. Follow the hosted link steps. - Wait for
card_application.status_changed.
A share token works once. Create a new token for each application attempt, just before you call Agio.
Create a share token
Call Sumsub's POST /resources/accessTokens/shareToken from your server, signed with your Sumsub app token and secret key. The Sumsub signature is the hex HMAC-SHA256 of the timestamp in seconds, the HTTP method, the path, and the raw body, in that order. These examples use the partner request helper only in the next step.
python
import hashlib
import hmac
import json
import os
import time
import httpx
SUMSUB_BASE_URL = "https://api.sumsub.com"
SUMSUB_APP_TOKEN = os.environ["SUMSUB_APP_TOKEN"]
SUMSUB_SECRET_KEY = os.environ["SUMSUB_SECRET_KEY"].encode()
AGIO_SUMSUB_CLIENT_ID = os.environ["AGIO_SUMSUB_CLIENT_ID"]
def create_share_token(applicant_id: str, ttl_seconds: int = 600) -> str:
path = "/resources/accessTokens/shareToken"
body = json.dumps({"applicantId": applicant_id, "forClientId": AGIO_SUMSUB_CLIENT_ID, "ttlInSecs": ttl_seconds}).encode()
timestamp = str(int(time.time()))
signature = hmac.new(SUMSUB_SECRET_KEY, timestamp.encode() + b"POST" + path.encode() + body, hashlib.sha256).hexdigest()
response = httpx.post(
SUMSUB_BASE_URL + path,
content=body,
timeout=30,
headers={
"content-type": "application/json",
"X-App-Token": SUMSUB_APP_TOKEN,
"X-App-Access-Ts": timestamp,
"X-App-Access-Sig": signature,
},
)
response.raise_for_status()
return response.json()["token"]go
package partner
import (
"bytes"
"context"
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"net/http"
"os"
"strconv"
"time"
)
func CreateShareToken(ctx context.Context, applicantID string, ttlSeconds int) (string, error) {
const path = "/resources/accessTokens/shareToken"
body, err := json.Marshal(map[string]any{
"applicantId": applicantID,
"forClientId": os.Getenv("AGIO_SUMSUB_CLIENT_ID"),
"ttlInSecs": ttlSeconds,
})
if err != nil {
return "", err
}
timestamp := strconv.FormatInt(time.Now().Unix(), 10)
mac := hmac.New(sha256.New, []byte(os.Getenv("SUMSUB_SECRET_KEY")))
mac.Write([]byte(timestamp + http.MethodPost + path))
mac.Write(body)
req, err := http.NewRequestWithContext(ctx, http.MethodPost, "https://api.sumsub.com"+path, bytes.NewReader(body))
if err != nil {
return "", err
}
req.Header.Set("content-type", "application/json")
req.Header.Set("X-App-Token", os.Getenv("SUMSUB_APP_TOKEN"))
req.Header.Set("X-App-Access-Ts", timestamp)
req.Header.Set("X-App-Access-Sig", hex.EncodeToString(mac.Sum(nil)))
resp, err := httpClient.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("sumsub returned HTTP %d", resp.StatusCode)
}
var out struct {
Token string `json:"token"`
}
if err := json.NewDecoder(resp.Body).Decode(&out); err != nil {
return "", err
}
return out.Token, nil
}The Go example reuses the shared httpClient from the quickstart. applicantId is the Sumsub applicant ID in your workspace, not the Agio cardUserId. Keep the Sumsub secret key on your server.
Submit the application with the token
Send the same input as a standard application and add partnerKycShareToken.
python
from partner_request import partner_query
from sumsub_share import create_share_token
APPLY = """
mutation Apply($input: CreatePartnerCardApplicationInput!) {
createCardApplicationForPartnerUser(input: $input) {
success
cardApplicationId
applicationStatus
applicationCompletionUrl
errorCode
error
}
}
"""
def apply_with_share_token(card_user_id: str, sumsub_applicant_id: str, application_input: dict) -> dict:
variables = {"input": {
**application_input,
"cardUserId": card_user_id,
"partnerKycShareToken": create_share_token(sumsub_applicant_id),
}}
result = partner_query(APPLY, variables)["createCardApplicationForPartnerUser"]
if not result["success"]:
raise RuntimeError(f"{result['errorCode']}: {result['error']}")
return resultgo
const applyMutation = `
mutation Apply($input: CreatePartnerCardApplicationInput!) {
createCardApplicationForPartnerUser(input: $input) {
success
cardApplicationId
applicationStatus
applicationCompletionUrl
errorCode
error
}
}`
type ApplyResult struct {
Success bool `json:"success"`
CardApplicationID *int `json:"cardApplicationId"`
ApplicationStatus string `json:"applicationStatus"`
ApplicationCompletionURL *string `json:"applicationCompletionUrl"`
ErrorCode *string `json:"errorCode"`
Error *string `json:"error"`
}
func ApplyWithShareToken(ctx context.Context, cardUserID, sumsubApplicantID string, input map[string]any) (*ApplyResult, error) {
token, err := CreateShareToken(ctx, sumsubApplicantID, 600)
if err != nil {
return nil, err
}
input["cardUserId"] = cardUserID
input["partnerKycShareToken"] = token
var out struct {
Result ApplyResult `json:"createCardApplicationForPartnerUser"`
}
if err := PartnerQuery(ctx, applyMutation, map[string]any{"input": input}, &out); err != nil {
return nil, err
}
if !out.Result.Success {
return nil, fmt.Errorf("application failed: %v", deref(out.Result.ErrorCode))
}
return &out.Result, nil
}
func deref(s *string) string {
if s == nil {
return ""
}
return *s
}Errors
errorCode | Meaning | Action |
|---|---|---|
KYC_TOKEN_EXPIRED | The token expired, was already used, or is not for Agio | Create a new token with Agio's client ID and submit again |
KYC_PROVIDER_NOT_SUPPORTED | The token is not a Sumsub share token | Send a Sumsub token, or use a hosted link |
KYC_WORKSPACE_NOT_AUTHORIZED | Agio has not registered your Sumsub workspace | Contact Agio onboarding |
Test in development
Use Agio development credentials and a Sumsub sandbox workspace that Agio registered for development. In development, the card issuer sets the application status from the cardholder's last name. A last name that contains Approved approves the application. A last name that contains NeedsVerification, with no space, asks the cardholder for more verification.
- In your Sumsub sandbox, finish verification for a test applicant.
- Provision a cardholder with the same details and the last name
TestApproved. - Submit the application with a new share token. Expect
success: trueand noapplicationCompletionUrl. - Submit a second time with the same token for another test cardholder. Expect
KYC_TOKEN_EXPIRED. - Provision a cardholder with the last name
TestNeedsVerificationand submit with a new token. ExpectapplicationCompletionUrl. Test the hosted link steps with it. - Check that your receiver gets
card_application.status_changedfor each application.