Integrations
Go
Server-side steps in Go.
Prerequisites
- Go with the standard library only.
- The secret key of the scenario and a permanent API token, read from environment variables. Never put them in browser code or in a repository.
- The API address provided with your access.
The call sequence
- Your server encrypts the raw client key with the secret key of the scenario.
- Your page opens the widget with the scenario identifier and the encrypted client key.
- The user completes the steps in the widget.
- Your server learns of the result from the
SESSION_COMPLETEwebhook, or reads session status. - Your server decides on the customer from the server-side status.
Encrypt the client key
The secret key of the scenario never leaves your server. See working with clientKey for the algorithm.
package verification
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
)
func pkcs7Pad(data []byte, blockSize int) []byte {
n := blockSize - len(data)%blockSize
return append(data, bytes.Repeat([]byte{byte(n)}, n)...)
}
// EncryptClientKey returns the clientKey for the widget.
func EncryptClientKey(clientKeyRaw, secret string) (string, error) {
// First 32 characters of the SHA-256 hash of the secret, written as hex.
sum := sha256.Sum256([]byte(secret))
key := []byte(hex.EncodeToString(sum[:])[:32])
block, err := aes.NewCipher(key)
if err != nil {
return "", err
}
iv := make([]byte, aes.BlockSize)
if _, err := rand.Read(iv); err != nil {
return "", err
}
plain := pkcs7Pad([]byte(clientKeyRaw), aes.BlockSize)
encrypted := make([]byte, len(plain))
cipher.NewCBCEncrypter(block, iv).CryptBlocks(encrypted, plain)
return base64.StdEncoding.EncodeToString(append(iv, encrypted...)), nil
}
Store the raw value with your user or order, so that you can match the result later.
Create a session
package verification
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"os"
)
func apiPost(path string, body map[string]any) (map[string]any, error) {
payload, err := json.Marshal(body)
if err != nil {
return nil, err
}
reply, err := http.Post(os.Getenv("API_BASE_URL")+path, "application/json", bytes.NewReader(payload))
if err != nil {
return nil, err
}
defer reply.Body.Close()
var out map[string]any
if err := json.NewDecoder(reply.Body).Decode(&out); err != nil {
return nil, err
}
return out, nil
}
func CreateSession() (map[string]any, error) {
session, err := apiPost("/kyc/session/create", map[string]any{
"token": os.Getenv("API_TOKEN"), // permanent API token
"schemaId": os.Getenv("SCENARIO_ID"), // scenario identifier
})
if err != nil {
return nil, err
}
if _, ok := session["sessionId"]; !ok {
// A failure arrives as a body with status "error" or "failed".
return nil, fmt.Errorf("session was not created: %v", session["status"])
}
return session, nil // session["status"] is the session status, "idle"
}
Read the result with apiPost("/kyc/session/status", ...) and branch on status, never on message text. See response format and errors and session statuses.
Troubleshooting
| Symptom | What to check |
|---|---|
| The widget does not open | The domain is in the allowed domains, the page is on HTTPS, and the widget is not inside a cross-origin iframe. See installation. |
| The widget reports that decryption failed | The client key was not encrypted, or a different secret key was used, or the string was cut. See working with clientKey. |
| A call returns success but the body has an error | Branch on the status field of the body. See response format and errors. |
| The result never reaches your server | Check the callback URL in the scenario. See webhooks. |