Go SDK

The official RDCPASS SDK for Go, published on Go modules as github.com/rdcpass/rdcpass-go. It signs every request, encrypts and decrypts payloads with AES-256-GCM, presents your mTLS client certificate, adds idempotency keys, retries transient failures and verifies webhooks — the complete security stack described in Authentication, handled for you.

Installation

Add github.com/rdcpass/rdcpass-go to your project:

go get github.com/rdcpass/rdcpass-go

Requirements

  • Go 1.21+ or later.
  • Outbound HTTPS (TLS 1.2 or later) to gateway.staging.rdcpass.cd for the staging environment and api.rdcpass.cd for production. In production, calls must come from an IP address on your application’s allowlist.
  • An RDCPASS application with API credentials, created in the console — see Sandbox.

Configuration

Create one client and reuse it across your application: it is safe for concurrent use and keeps connections open between calls. The environment selects the base URL; keys and certificates issued for one environment are rejected by the other.

EnvironmentRDCPASS_ENVIRONMENTBase URL
Staging environmentsandboxhttps://gateway.staging.rdcpass.cd
Productionproductionhttps://api.rdcpass.cd

Configure a client for the staging environment explicitly:

client.go
package main

import (
	"log"
	"os"

	rdcpass "github.com/rdcpass/rdcpass-go"
)

func newClient() *rdcpass.Client {
	// Staging environment: https://gateway.staging.rdcpass.cd
	client, err := rdcpass.NewClient(rdcpass.Config{
		Environment:   rdcpass.Sandbox,
		KeyID:         "key_test_3c7e1a9f42",
		KeySecret:     os.Getenv("RDCPASS_KEY_SECRET"),
		EncryptionKey: os.Getenv("RDCPASS_ENCRYPTION_KEY"),
	})
	if err != nil {
		log.Fatal(err)
	}
	return client
}

Credentials

CredentialEnvironment variableDescription
Key IDRDCPASS_KEY_IDPublic identifier of your HMAC key, sent with every request (key_test_… in staging, key_live_… in production).
Key secretRDCPASS_KEY_SECRETHMAC-SHA256 signing secret. Never sent over the network.
Payload Encryption KeyRDCPASS_ENCRYPTION_KEYAES-256-GCM key used to encrypt requests and decrypt responses, and to verify webhook signatures.
EnvironmentRDCPASS_ENVIRONMENTsandbox or production. Defaults to sandbox.
mTLS client certificate and key—Issued by RDCPASS for your application; required in production. Must belong to the same application as the key ID. Loaded from files in the client configuration.

Production and environment variables

When no credentials are passed, the client reads the four environment variables above. In production, add your mTLS client certificate and private key:

client.go
// Fields left empty are read from RDCPASS_KEY_ID, RDCPASS_KEY_SECRET,
// RDCPASS_ENCRYPTION_KEY and RDCPASS_ENVIRONMENT.
// Production (https://api.rdcpass.cd) also requires your mTLS client certificate.
client, err := rdcpass.NewClient(rdcpass.Config{
	ClientCertFile: "/etc/rdcpass/client.crt",
	ClientKeyFile:  "/etc/rdcpass/client.key",
})
if err != nil {
	log.Fatal(err)
}

Keep secrets out of source code

Load the key secret, the Payload Encryption Key and the certificate’s private key from environment variables or a secrets manager. Never commit them, and never ship them in a mobile or browser application — the SDK is for server-side use.

Your first request

Validate a customer by RDCPASS ID with KYC Validation. The SDK encrypts and signs the request, decrypts the response and returns a typed kyc_validation object:

main.go
package main

import (
	"context"
	"fmt"
	"log"

	rdcpass "github.com/rdcpass/rdcpass-go"
)

func main() {
	ctx := context.Background()

	client, err := rdcpass.NewClient(rdcpass.Config{})
	if err != nil {
		log.Fatal(err)
	}

	validation, err := client.KYC.Validations.Create(ctx, &rdcpass.KYCValidationParams{
		Reference:  "cust-0001",
		Identifier: rdcpass.Identifier{Type: rdcpass.IdentifierRDCPASSID, Value: "COD-2103-0214-4937"},
		Match:      &rdcpass.Match{FullName: "Kabeya Mwamba Tshisekedi", DateOfBirth: "1988-04-12"},
		Purpose:    "customer_onboarding",
		Scopes:     []string{"kyc.documents.primary", "kyc.phone_numbers"},
	})
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(validation.Result)             // verified
	fmt.Println(validation.KYC.Basic.FullName) // Kabeya Mwamba Tshisekedi
	fmt.Println(validation.ScopesWithheld)     // []
}
Decrypted response
{
  "id": "kyc_8d2f6a1c93",
  "object": "kyc_validation",
  "livemode": true,
  "reference": "cust-0001",
  "status": "completed",
  "result": "verified",
  "certified_account": true,
  "identifier": {
    "type": "rdcpass_id",
    "value": "COD-2103-0214-4937"
  },
  "match": {
    "full_name": "match",
    "date_of_birth": "match",
    "age": "not_provided",
    "score": 0.98
  },
  "account": {
    "rdcpass_id": "COD-2103-0214-4937",
    "status": "active",
    "certified": true,
    "created_at": "2025-03-14T09:22:41Z",
    "level_of_assurance": "LOA3"
  },
  "kyc": {
    "basic": {
      "full_name": "Kabeya Mwamba Tshisekedi",
      "first_name": "Kabeya",
      "last_name": "Tshisekedi",
      "date_of_birth": "1988-04-12",
      "age": 38,
      "gender": "male",
      "nationality": "COD"
    },
    "documents": {
      "primary": {
        "type": "passport",
        "number": "OB1234567",
        "issued_at": "2022-06-01",
        "expires_at": "2027-05-31",
        "issuing_authority": "Direction Générale de Migration",
        "status": "valid",
        "image": {
          "content_type": "image/jpeg",
          "url": "https://files.rdcpass.cd/d/9f2c41e8b7a3?sig=Qm9fX2t5Y19zaWc&exp=1790418002",
          "expires_at": "2026-09-26T10:20:02Z"
        }
      }
    },
    "phone_numbers": [
      {
        "number": "+243812345678",
        "operator": "Vodacom",
        "is_primary": true,
        "verified": true
      },
      {
        "number": "+243991234567",
        "operator": "Airtel",
        "is_primary": false,
        "verified": true
      }
    ]
  },
  "scopes_applied": [
    "kyc.basic",
    "kyc.documents.primary",
    "kyc.phone_numbers"
  ],
  "scopes_withheld": [],
  "purpose": "customer_onboarding",
  "created_at": "2026-09-26T10:15:02Z"
}

Every field is documented in KYC Validation.

Calling every service

Every service is exposed as a resource on the client, named after its endpoint. Parameters and responses use the same names and shapes as the API reference.

KYC Validation

Look up an identity by RDCPASS ID, passport, CENI voter card, driving licence or national ID. The result is verified, not_found or not_certified. Reference →

validation, err := client.KYC.Validations.Create(ctx, &rdcpass.KYCValidationParams{
	Identifier: rdcpass.Identifier{Type: rdcpass.IdentifierPassport, Value: "OB1234567"},
	Purpose:    "customer_onboarding",
	Scopes:     []string{}, // basic KYC only
})
if err != nil {
	return err
}
fmt.Println(validation.Result) // verified | not_found | not_certified

Face Recognition

Compare a photo — sent as base64 data or a url — with the enrolled face of an identity. reason is required; the result is match or no_match. Reference →

photo, err := os.ReadFile("selfie.jpg")
if err != nil {
	return err
}
recognition, err := client.Face.Recognitions.Create(ctx, &rdcpass.FaceRecognitionParams{
	Image:      rdcpass.FaceImage{Data: base64.StdEncoding.EncodeToString(photo)},
	Identifier: rdcpass.Identifier{Type: rdcpass.IdentifierRDCPASSID, Value: "COD-2103-0214-4937"},
	Reason:     "Current account opening at Gombe branch, Kinshasa",
	Purpose:    "customer_onboarding",
})
if err != nil {
	return err
}
fmt.Println(recognition.Result) // match | no_match

Age Verification

Check whether a person is over a threshold without receiving their date of birth; read over_threshold. Reference →

verification, err := client.Age.Verifications.Create(ctx, &rdcpass.AgeVerificationParams{
	Identifier: rdcpass.Identifier{Type: rdcpass.IdentifierRDCPASSID, Value: "COD-2103-0214-4937"},
	Threshold:  18,
	Purpose:    "age_gating",
})
if err != nil {
	return err
}
fmt.Println(verification.OverThreshold) // true

KYB Verification

Verify a business by rccm, id_nat or nif, and receive its officers and beneficial owners if your application is granted those scopes. Reference →

business, err := client.KYB.Verifications.Create(ctx, &rdcpass.KYBVerificationParams{
	Identifier: rdcpass.BusinessIdentifier{Type: rdcpass.BusinessIdentifierRCCM, Value: "CD/KIN/RCCM/23-B-01234"},
	Match:      &rdcpass.BusinessMatch{BusinessName: "Congo Digital Solutions SARL"},
	Purpose:    "customer_onboarding",
	Scopes:     []string{"kyb.officers", "kyb.beneficial_owners"},
})
if err != nil {
	return err
}
fmt.Println(business.Result)

AML/CTF Screening

Screen a subject against the requested checks; enable monitoring to be notified of new hits. Read risk_level. Reference →

screening, err := client.AML.Screenings.Create(ctx, &rdcpass.AMLScreeningParams{
	Subject: rdcpass.AMLSubject{
		Identifier: rdcpass.Identifier{Type: rdcpass.IdentifierRDCPASSID, Value: "COD-1907-5521-0386"},
	},
	Checks:     []string{"sanctions", "pep", "adverse_media", "watchlists", "high_risk_jurisdictions"},
	Monitoring: true,
	Purpose:    "regulatory_compliance",
})
if err != nil {
	return err
}
fmt.Println(screening.RiskLevel) // low | medium | high

Credit Scoring

Requires the customer’s consent in the RDCPASS app (consent.method: rdcpass_app). Returns a score and a band. Reference →

credit, err := client.Credit.Scores.Create(ctx, &rdcpass.CreditScoreParams{
	Identifier: rdcpass.Identifier{Type: rdcpass.IdentifierRDCPASSID, Value: "COD-2103-0214-4937"},
	Consent:    rdcpass.Consent{Method: "rdcpass_app", ConsentID: "cns_5e8a2d71c4"},
	Purpose:    "credit_assessment",
})
if err != nil {
	return err
}
fmt.Println(credit.Score, credit.Band)

Fraud Reporting

Report a confirmed or suspected fraud, such as identity_usurpation, against a subject, with a description. Reference →

report, err := client.Fraud.Reports.Create(ctx, &rdcpass.FraudReportParams{
	Type: "identity_usurpation",
	Subject: rdcpass.FraudSubject{
		Identifier: rdcpass.Identifier{Type: rdcpass.IdentifierRDCPASSID, Value: "COD-2103-0214-4937"},
	},
	Description: "Account opened with a stolen passport at an agent in Lubumbashi.",
})
if err != nil {
	return err
}
fmt.Println(report.ID, report.Status)

Login with RDCPASS

The login helper builds the /v1/oidc/authorize URL with state and a PKCE code_challenge (S256), exchanges the authorization code at /v1/oidc/token with the matching code_verifier, and calls /v1/oidc/userinfo. See Login with RDCPASS.

login.go
// 1. Build the authorize URL. The SDK generates state and the PKCE
//    code_verifier / code_challenge (S256) for you.
auth, err := client.Login.AuthorizationURL(&rdcpass.AuthorizationURLParams{
	ClientID:    "app_8f2a1c0e9b",
	RedirectURI: "https://yourapp.example.com/callback",
	Scopes:      []string{"openid", "profile", "rdcpass:kyc.phone_numbers"},
})
if err != nil {
	return err
}
// Store auth.State and auth.CodeVerifier in the user's session,
// then redirect to auth.URL.

// 2. On your callback, check state and exchange the code.
if r.URL.Query().Get("state") != session.State {
	return errors.New("state mismatch")
}
tokens, err := client.Login.ExchangeCode(ctx, &rdcpass.ExchangeCodeParams{
	ClientID:     "app_8f2a1c0e9b",
	Code:         r.URL.Query().Get("code"),
	CodeVerifier: session.CodeVerifier,
	RedirectURI:  "https://yourapp.example.com/callback",
})
if err != nil {
	return err
}
user, err := client.Login.UserInfo(ctx, tokens.AccessToken)

Batches & async

Every service accepts batches at /batches: up to 50 items synchronously, or up to 10,000 asynchronously. With the async option the SDK sends Prefer: respond-async and returns a 202 job or batch object immediately.

  • Wait — the wait helper polls /v1/batches/{id} (or /v1/jobs/{id} for a single async call) with backoff until the status is final, or until your timeout elapses.
  • Iterate — the results helper pages through /v1/batches/{id}/results and follows next_cursor for you.
  • Or subscribe — instead of polling, handle the batch.completed and batch.completed_with_errors webhooks.
refresh_kyc.go
// Submit up to 10,000 items asynchronously (Prefer: respond-async).
batch, err := client.KYC.Validations.Batches.Create(ctx, &rdcpass.KYCValidationBatchParams{
	Purpose: "customer_onboarding",
	Items: []rdcpass.KYCValidationParams{
		{Reference: "cust-0001", Identifier: rdcpass.Identifier{Type: rdcpass.IdentifierRDCPASSID, Value: "COD-2103-0214-4937"}},
		{Reference: "cust-0002", Identifier: rdcpass.Identifier{Type: rdcpass.IdentifierCENI, Value: "1234567890123"}},
	},
}, rdcpass.WithAsync())
if err != nil {
	return err
}

// Poll GET /v1/batches/{id} until the batch reaches a final status.
done, err := client.Batches.Wait(ctx, batch.ID, &rdcpass.WaitParams{
	PollInterval: 5 * time.Second,
	Timeout:      30 * time.Minute,
})
if err != nil {
	return err
}
fmt.Println(done.Status, done.SucceededItems, done.FailedItems)

// Iterate every result; the iterator follows next_cursor page by page.
iter := client.Batches.Results(ctx, batch.ID, &rdcpass.ListParams{Limit: 500})
for iter.Next() {
	item := iter.Current()
	fmt.Println(item.Reference, item.Status)
}
if err := iter.Err(); err != nil {
	return err
}

Batch quota is separate from single-call quota, and batch items are billed at the discounted batch rate — see Rate limits and Billing.

Webhooks

The webhook helper checks X-RDCPASS-Webhook-Signature — HMAC-SHA256 of {timestamp}.{rawBody} with your Payload Encryption Key — using a constant-time comparison, rejects deliveries whose X-RDCPASS-Webhook-Timestamp is more than 5 minutes from your clock (configurable), and parses the envelope { id, object: "event", type, livemode, created_at, data: { object, previous_attributes } } into a typed event.

webhooks.go
func rdcpassWebhook(w http.ResponseWriter, r *http.Request) {
	// Read the raw body: the signature covers "{timestamp}.{rawBody}".
	body, err := io.ReadAll(r.Body)
	if err != nil {
		http.Error(w, "cannot read body", http.StatusBadRequest)
		return
	}

	event, err := client.Webhooks.ConstructEvent(body, r.Header)
	if err != nil {
		http.Error(w, "invalid signature", http.StatusBadRequest)
		return
	}

	switch event.Type {
	case "kyc_validation.completed":
		var job rdcpass.Job
		if err := event.Data.Unmarshal(&job); err == nil {
			log.Println(job.ID, job.Status)
		}
	case "batch.completed", "batch.completed_with_errors":
		var batch rdcpass.Batch
		if err := event.Data.Unmarshal(&batch); err == nil {
			log.Println(batch.ID, batch.FailedItems)
		}
	}
	w.WriteHeader(http.StatusOK)
}

Pass the raw request body, exactly as received — a body parsed and re-serialized by your framework will not match the signature. Respond with a 2xx within 5 seconds and deduplicate on the event id, which is also sent as X-RDCPASS-Webhook-Id. See Webhooks.

Errors

Every API error — { error, message } in the response body — is raised as *rdcpass.Error, which carries Code, Status, Message, RequestID. Network failures and timeouts that exhaust their retries raise a separate connection error. Quote the request ID when you contact RDCPASS support.

CodeHTTPWhat to do
invalid_identifier400The identifier is malformed for its type. Fix the input; do not retry.
insufficient_balance402Your prepaid balance is too low. Top up in the console.
scope_not_granted403A requested scope is not granted to your application. Remove it or request it in the console.
purpose_not_approved403The purpose is not approved for your application.
rate_limited429Too many requests per second. Retried automatically after Retry-After.
batch_quota_exceeded429Your batch quota is used up. Separate from single-call quota.
_, err := client.KYC.Validations.Create(ctx, &rdcpass.KYCValidationParams{
	Identifier: rdcpass.Identifier{Type: rdcpass.IdentifierRDCPASSID, Value: "COD-2103-0214-4937"},
	Purpose:    "customer_onboarding",
	Scopes:     []string{"kyc.biometrics.iris"},
})

var apiErr *rdcpass.Error
if errors.As(err, &apiErr) {
	log.Println(apiErr.Status, apiErr.Code, apiErr.Message, apiErr.RequestID)
	if apiErr.Code == "scope_not_granted" {
		// 403: request the scope for your application in the console
	}
} else if err != nil {
	return err
}

The full list is in Errors.

Retries & idempotency

  • Automatic idempotency keys. Every POST is sent with a generated Idempotency-Key, reused on each retry of that request, so a retried call is never processed or billed twice.
  • What is retried. 429 responses, 5xx responses, connection failures and timeouts. Other 4xx errors are raised immediately.
  • Backoff. Exponential backoff with jitter, starting at 0.5 seconds and capped at 8 seconds; Retry-After is honoured when present. Up to 3 retries by default.
  • Configurable. Change the number of retries per client or per request, and pass your own idempotency key to keep retries safe across process restarts.
client, err := rdcpass.NewClient(rdcpass.Config{
	MaxRetries: 5, // default 3; set rdcpass.NoRetries to disable
})
if err != nil {
	return err
}

// Every POST gets an Idempotency-Key automatically. Pass your own to make
// retries safe across process restarts:
_, err = client.Fraud.Reports.Create(ctx, &rdcpass.FraudReportParams{
	Type: "identity_usurpation",
	Subject: rdcpass.FraudSubject{
		Identifier: rdcpass.Identifier{Type: rdcpass.IdentifierRDCPASSID, Value: "COD-2103-0214-4937"},
	},
	Description: "Account opened with a stolen passport.",
}, rdcpass.WithIdempotencyKey("fraud-case-INC-2026-88213"), rdcpass.WithMaxRetries(1))

Timeouts & logging

Each attempt times out after 30 seconds by default; set a different timeout per client or per request. Logs are written through your language’s standard logging interface. They include method, path, status, duration, retry attempts and request ID — never decrypted payloads, secrets or personal data.

client, err := rdcpass.NewClient(rdcpass.Config{
	Timeout: 20 * time.Second, // per attempt (default 30s)
	Logger:  slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelDebug})),
})
if err != nil {
	return err
}

// Deadlines and cancellation also follow the context you pass.
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()
_, err = client.AML.Screenings.Create(ctx, &rdcpass.AMLScreeningParams{
	Subject: rdcpass.AMLSubject{
		Identifier: rdcpass.Identifier{Type: rdcpass.IdentifierRDCPASSID, Value: "COD-1907-5521-0386"},
	},
	Purpose: "regulatory_compliance",
})

Upgrading & versioning

  • github.com/rdcpass/rdcpass-go follows semantic versioning: only major versions contain breaking changes.
  • Each major version receives security and bug fixes for 24 months after the next major version is released.
  • Every release, with upgrade notes for each major version, is listed in the Changelog.
  • New API fields and events arrive in minor releases, so keep the SDK up to date to use them.

Integrating without an SDK? See Authentication to sign, encrypt and send requests yourself.

Questions about your integration? Contact developer support