• SettleMintSettleMint
    • Introduction
    • Market pain points
    • Lifecycle platform approach
    • Platform capabilities
    • Use cases
    • Compliance & security
    • Glossary
    • Core component overview
    • Frontend layer
    • API layer
    • Blockchain layer
    • Data layer
    • Deployment layer
    • System architecture
    • Smart contracts
    • Application layer
    • Data & indexing
    • Integration & operations
    • Performance
    • Quality
    • Getting started
    • Asset issuance
    • Platform operations
    • Troubleshooting
    • Development environment
    • Code structure
    • Smart contracts
    • API integration
    • Data model
    • Deployment & ops
    • Testing and QA
    • Developer FAQ
Back to the application
  1. Documentation
  2. Architecture
  3. Integration operations

How to integrate external systems with ATK

ATK is designed as an open platform that integrates with external systems through well-defined integration patterns. This playbook covers payment rails, KYC providers, identity providers, custody solutions, and corporate action automation.

Integration architecture

Primary role: Technical architects, integration engineers

Secondary readers: Product managers planning integrations, developers implementing adapters

Integration layers

Rendering chart...

Integration principles

Adapter pattern:

Each external system integrates through a dedicated adapter that:

  • Translates between external API and ATK data models
  • Handles authentication and rate limiting
  • Implements retry logic and circuit breakers
  • Maintains integration state

Event-driven:

Integrations use events to trigger actions:

  • External webhook triggers ATK procedure
  • ATK emits event consumed by external system
  • Asynchronous processing via message queue

Idempotent operations:

All integration endpoints are idempotent:

  • Duplicate webhook deliveries are safe
  • Retry logic won't create duplicate state
  • Use unique transaction IDs to track operations

Payment rails integration

Supported payment rails

ATK supports integration with multiple payment systems for settlement:

Payment RailSettlement TimeCurrency SupportUse Case
Stablecoins (USDC, USDT)<1 minuteUSD, EURDvP settlement, instant transfers
RTGS (Real-Time Gross Settlement)1-2 hoursLocal currenciesLarge value domestic transfers
SWIFT1-5 days150+ currenciesCross-border transfers
SEPA1-2 daysEUREuropean domestic transfers
FedNow<1 minuteUSDUS instant payments
ACH1-3 daysUSDUS domestic batched transfers

Stablecoin settlement integration

Architecture:

Rendering chart...

Implementation:

// kit/dapp/src/integrations/payment/stablecoin-adapter.ts
import { viem } from "@/lib/viem";
import { parseUnits, Address } from "viem";

export class StablecoinAdapter {
  constructor(
    private readonly usdcContractAddress: Address,
    private readonly tokenContractAddress: Address
  ) {}

  async initiateDvPSettlement(params: {
    buyer: Address;
    seller: Address;
    tokenAmount: bigint;
    usdcAmount: bigint;
  }) {
    // 1. Verify buyer has sufficient USDC balance
    const balance = await viem.readContract({
      address: this.usdcContractAddress,
      abi: ERC20_ABI,
      functionName: "balanceOf",
      args: [params.buyer],
    });

    if (balance < params.usdcAmount) {
      throw new Error("Insufficient USDC balance");
    }

    // 2. Initiate atomic DvP settlement
    const hash = await viem.writeContract({
      address: this.tokenContractAddress,
      abi: DVP_ABI,
      functionName: "executeDvP",
      args: [
        params.buyer,
        params.seller,
        params.tokenAmount,
        this.usdcContractAddress,
        params.usdcAmount,
      ],
    });

    // 3. Wait for settlement confirmation
    const receipt = await viem.waitForTransactionReceipt({ hash });

    return {
      settlementId: hash,
      status: receipt.status === "success" ? "completed" : "failed",
      timestamp: new Date(),
    };
  }
}

Configuration:

// Environment variables
USDC_CONTRACT_ADDRESS=0x... // Circle USDC contract
USDT_CONTRACT_ADDRESS=0x... // Tether USDT contract
DVP_CONTRACT_ADDRESS=0x... // ATK DvP settlement contract

RTGS/SWIFT integration

Architecture:

RTGS and SWIFT require bank-side integration. ATK provides webhook endpoints for payment notifications.

Rendering chart...

Webhook handler:

// kit/dapp/src/orpc/procedures/webhooks/payment.ts
import { z } from "zod";
import { procedure } from "../middleware";

const paymentWebhookSchema = z.object({
  referenceId: z.string(), // ATK-TX-XXXXX
  amount: z.number(),
  currency: z.enum(["USD", "EUR", "GBP"]),
  bankTransactionId: z.string(),
  timestamp: z.string(),
  signature: z.string(), // HMAC signature
});

export const handlePaymentWebhook = procedure
  .input(paymentWebhookSchema)
  .mutation(async ({ input, ctx }) => {
    // 1. Verify webhook signature
    const isValid = verifyHMAC(input, process.env.BANK_WEBHOOK_SECRET!);
    if (!isValid) {
      throw new Error("Invalid webhook signature");
    }

    // 2. Find pending order by reference
    const order = await ctx.db
      .select()
      .from(orders)
      .where(eq(orders.referenceId, input.referenceId))
      .limit(1);

    if (!order.length) {
      throw new Error("Order not found");
    }

    // 3. Verify payment amount matches
    if (order[0].expectedAmount !== input.amount) {
      throw new Error("Amount mismatch");
    }

    // 4. Mint tokens for investor
    await ctx.txSigner.writeContract({
      address: order[0].tokenAddress,
      functionName: "mint",
      args: [order[0].investorAddress, order[0].tokenAmount],
    });

    // 5. Mark order as completed
    await ctx.db
      .update(orders)
      .set({
        status: "completed",
        paymentProof: input.bankTransactionId,
        completedAt: new Date(input.timestamp),
      })
      .where(eq(orders.id, order[0].id));

    return { success: true, orderId: order[0].id };
  });

Security configuration:

// Webhook authentication
BANK_WEBHOOK_SECRET=<SharedSecret>
BANK_WEBHOOK_IP_WHITELIST=203.0.113.0/24

// Configure in bank admin panel
WEBHOOK_URL=https://atk.example.com/api/webhooks/payment
WEBHOOK_EVENTS=["payment.completed", "payment.failed"]

SEPA Direct Debit integration

For recurring payments (e.g., fund subscriptions):

// kit/dapp/src/integrations/payment/sepa-adapter.ts
export class SEPAAdapter {
  async createDirectDebitMandate(params: {
    investorId: string;
    iban: string;
    creditorId: string;
    mandateRef: string;
  }) {
    // Generate SEPA XML mandate
    const mandate = generateSEPAMandate({
      debtor: { iban: params.iban },
      creditor: { id: params.creditorId },
      mandateId: params.mandateRef,
      signatureDate: new Date().toISOString().split("T")[0],
    });

    // Store mandate in database
    await ctx.db.insert(paymentMandates).values({
      investorId: params.investorId,
      mandateRef: params.mandateRef,
      iban: params.iban,
      status: "pending_signature",
      mandateXml: mandate,
    });

    return { mandateRef: params.mandateRef, xmlDocument: mandate };
  }

  async initiateDirectDebit(params: {
    mandateRef: string;
    amount: number;
    currency: "EUR";
    description: string;
  }) {
    // Create SEPA Direct Debit XML
    const debitXml = generateSEPADebit({
      mandateRef: params.mandateRef,
      amount: params.amount,
      currency: params.currency,
      remittanceInfo: params.description,
      executionDate: addDays(new Date(), 1), // Next business day
    });

    // Submit to bank via SFTP or API
    await bankAPI.submitDirectDebit(debitXml);

    return { debitId: generateId(), status: "pending" };
  }
}

KYC provider integration

KYC provider adapter pattern

Supported providers:

  • Sumsub - Automated identity verification
  • Jumio - Document verification and biometrics
  • Onfido - Identity checks and AML screening
  • Trulioo - Global identity verification
  • ComplyAdvantage - AML/sanctions screening

Adapter interface:

// kit/dapp/src/integrations/kyc/kyc-provider.interface.ts
export interface IKYCProvider {
  // Submit KYC application to provider
  submitApplication(params: {
    applicantId: string;
    firstName: string;
    lastName: string;
    dateOfBirth: string;
    nationality: string;
    documentType: "passport" | "drivers_license" | "id_card";
    documentImages: Buffer[];
  }): Promise<{ verificationId: string }>;

  // Check verification status
  getVerificationStatus(verificationId: string): Promise<{
    status: "pending" | "approved" | "rejected" | "review";
    reason?: string;
    completedAt?: Date;
  }>;

  // Handle webhook from provider
  handleWebhook(
    payload: unknown,
    signature: string
  ): Promise<{
    verificationId: string;
    status: string;
    investorId: string;
  }>;
}

Sumsub integration example

Rendering chart...

Implementation:

// kit/dapp/src/integrations/kyc/sumsub-adapter.ts
import crypto from "crypto";
import axios from "axios";

export class SumsubAdapter implements IKYCProvider {
  private readonly baseURL = "https://api.sumsub.com";
  private readonly appToken = process.env.SUMSUB_APP_TOKEN!;
  private readonly secretKey = process.env.SUMSUB_SECRET_KEY!;

  async submitApplication(params: {
    applicantId: string;
    firstName: string;
    lastName: string;
    dateOfBirth: string;
    nationality: string;
    documentType: string;
    documentImages: Buffer[];
  }) {
    const endpoint = "/resources/applicants";
    const method = "POST";

    // Generate Sumsub authentication signature
    const timestamp = Math.floor(Date.now() / 1000);
    const signature = this.generateSignature(method, endpoint, timestamp);

    // Create applicant
    const response = await axios.post(
      `${this.baseURL}${endpoint}`,
      {
        externalUserId: params.applicantId,
        fixedInfo: {
          firstName: params.firstName,
          lastName: params.lastName,
          dob: params.dateOfBirth,
          country: params.nationality,
        },
        requiredIdDocs: {
          docSets: [
            {
              idDocSetType: params.documentType.toUpperCase(),
              types: ["IDENTITY"],
            },
          ],
        },
      },
      {
        headers: {
          "X-App-Token": this.appToken,
          "X-App-Access-Sig": signature,
          "X-App-Access-Ts": timestamp.toString(),
        },
      }
    );

    // Upload document images
    for (const image of params.documentImages) {
      await this.uploadDocument(response.data.id, image);
    }

    return { verificationId: response.data.id };
  }

  async handleWebhook(payload: any, signature: string) {
    // Verify webhook signature
    const isValid = this.verifyWebhookSignature(payload, signature);
    if (!isValid) {
      throw new Error("Invalid webhook signature");
    }

    // Extract verification data
    const { applicantId, reviewResult, externalUserId } = payload;

    return {
      verificationId: applicantId,
      status: reviewResult.reviewAnswer, // GREEN, RED, YELLOW
      investorId: externalUserId,
    };
  }

  private generateSignature(
    method: string,
    endpoint: string,
    timestamp: number
  ): string {
    const message = `${timestamp}${method}${endpoint}`;
    return crypto
      .createHmac("sha256", this.secretKey)
      .update(message)
      .digest("hex");
  }

  private verifyWebhookSignature(payload: any, signature: string): boolean {
    const calculatedSig = crypto
      .createHmac("sha256", this.secretKey)
      .update(JSON.stringify(payload))
      .digest("hex");

    return crypto.timingSafeEqual(
      Buffer.from(signature),
      Buffer.from(calculatedSig)
    );
  }
}

Configuration:

// Environment variables
SUMSUB_APP_TOKEN=<ApplicationToken>
SUMSUB_SECRET_KEY=<SecretKey>
SUMSUB_WEBHOOK_SECRET=<WebhookSecret>

// Configure in Sumsub dashboard
WEBHOOK_URL=https://atk.example.com/api/webhooks/kyc
WEBHOOK_EVENTS=["applicantReviewed", "applicantPending"]

AML screening integration

For ongoing AML monitoring:

// kit/dapp/src/integrations/kyc/aml-screening.ts
import { ComplyAdvantageAPI } from "@complyadvantage/api";

export class AMLScreeningAdapter {
  private readonly client = new ComplyAdvantageAPI(
    process.env.COMPLYADVANTAGE_API_KEY!
  );

  async screenInvestor(params: {
    investorId: string;
    fullName: string;
    dateOfBirth: string;
    nationality: string;
  }) {
    // Search sanctions, PEP, and adverse media lists
    const searchResult = await this.client.searches.create({
      search_term: params.fullName,
      fuzziness: 0.7,
      filters: {
        birth_year: parseInt(params.dateOfBirth.split("-")[0]),
        types: ["sanction", "warning", "fitness-probity", "pep"],
      },
    });

    // Parse results
    const hits = searchResult.data.filter((result: any) =>
      result.match_types.includes("name_exact")
    );

    if (hits.length > 0) {
      // Store alert for compliance officer review
      await ctx.db.insert(amlAlerts).values({
        investorId: params.investorId,
        alertType: "potential_match",
        matchData: JSON.stringify(hits),
        status: "pending_review",
        createdAt: new Date(),
      });

      return { risk: "high", matches: hits.length };
    }

    return { risk: "low", matches: 0 };
  }

  async schedulePeriodicScreening(investorId: string) {
    // Create recurring screening job
    await ctx.db.insert(screeningSchedules).values({
      investorId,
      frequency: "monthly",
      lastScreenedAt: new Date(),
      nextScreenAt: addMonths(new Date(), 1),
    });
  }
}

External identity provider integration

OAuth 2.0 / OpenID Connect integration

Integrate with corporate identity providers for SSO:

// kit/dapp/src/integrations/identity/oauth-adapter.ts
import { OAuth2Client } from "google-auth-library";

export class OAuthAdapter {
  private readonly client = new OAuth2Client(
    process.env.OAUTH_CLIENT_ID,
    process.env.OAUTH_CLIENT_SECRET,
    process.env.OAUTH_REDIRECT_URI
  );

  async initiateLogin() {
    const authUrl = this.client.generateAuthUrl({
      access_type: "offline",
      scope: ["openid", "email", "profile"],
      state: generateSecureToken(), // CSRF protection
    });

    return { loginUrl: authUrl };
  }

  async handleCallback(code: string, state: string) {
    // Verify state parameter (CSRF protection)
    const isValidState = await verifyStateToken(state);
    if (!isValidState) {
      throw new Error("Invalid state parameter");
    }

    // Exchange authorization code for tokens
    const { tokens } = await this.client.getToken(code);
    this.client.setCredentials(tokens);

    // Fetch user info
    const userInfo = await this.client.verifyIdToken({
      idToken: tokens.id_token!,
      audience: process.env.OAUTH_CLIENT_ID,
    });

    const payload = userInfo.getPayload();

    // Create or update user in ATK
    const user = await ctx.db
      .insert(users)
      .values({
        email: payload.email!,
        firstName: payload.given_name,
        lastName: payload.family_name,
        externalId: payload.sub,
        provider: "oauth",
      })
      .onConflictDoUpdate({
        target: users.email,
        set: { lastLoginAt: new Date() },
      });

    return { userId: user.id, email: payload.email };
  }
}

SAML 2.0 integration

For enterprise SSO with SAML:

// kit/dapp/src/integrations/identity/saml-adapter.ts
import * as saml2 from "saml2-js";

export class SAMLAdapter {
  private readonly sp = new saml2.ServiceProvider({
    entity_id: process.env.SAML_ENTITY_ID!,
    private_key: process.env.SAML_PRIVATE_KEY!,
    certificate: process.env.SAML_CERTIFICATE!,
    assert_endpoint: process.env.SAML_ACS_URL!,
  });

  private readonly idp = new saml2.IdentityProvider({
    sso_login_url: process.env.SAML_SSO_URL!,
    sso_logout_url: process.env.SAML_LOGOUT_URL!,
    certificates: [process.env.SAML_IDP_CERT!],
  });

  async initiateLogin() {
    const loginUrl = this.sp.create_login_request_url(this.idp, {});
    return { loginUrl };
  }

  async handleAssertion(samlResponse: string) {
    return new Promise((resolve, reject) => {
      this.sp.post_assert(
        this.idp,
        { SAMLResponse: samlResponse },
        (err, result) => {
          if (err) {
            return reject(err);
          }

          // Extract user attributes
          const { name_id, email, given_name, family_name, groups } =
            result.user;

          // Create/update user with mapped roles
          const user = ctx.db.insert(users).values({
            email,
            firstName: given_name,
            lastName: family_name,
            externalId: name_id,
            provider: "saml",
            roles: mapGroupsToRoles(groups),
          });

          resolve({ userId: user.id, email });
        }
      );
    });
  }
}

Third-party custody integration

Fireblocks integration

For institutional custody:

// kit/dapp/src/integrations/custody/fireblocks-adapter.ts
import { FireblocksSDK } from "fireblocks-sdk";
import { readFileSync } from "fs";

export class FireblocksAdapter {
  private readonly client = new FireblocksSDK(
    readFileSync(process.env.FIREBLOCKS_PRIVATE_KEY_PATH!, "utf8"),
    process.env.FIREBLOCKS_API_KEY!
  );

  async createVaultAccount(investorId: string, name: string) {
    const vault = await this.client.createVaultAccount({
      name: `${name}-${investorId}`,
      customerRefId: investorId,
      autoFuel: true,
    });

    return { vaultId: vault.id };
  }

  async executeTransaction(params: {
    vaultId: string;
    assetId: string;
    operation: "MINT" | "TRANSFER" | "BURN";
    amount: string;
    destination?: string;
  }) {
    const tx = await this.client.createTransaction({
      operation: params.operation,
      source: {
        type: "VAULT_ACCOUNT",
        id: params.vaultId,
      },
      destination: params.destination
        ? {
            type: "EXTERNAL_WALLET",
            oneTimeAddress: { address: params.destination },
          }
        : undefined,
      assetId: params.assetId,
      amount: params.amount,
      note: `ATK ${params.operation}`,
    });

    return { transactionId: tx.id, status: tx.status };
  }

  async handleWebhook(payload: any, signature: string) {
    // Verify webhook signature
    const isValid = this.verifyWebhookSignature(payload, signature);
    if (!isValid) {
      throw new Error("Invalid Fireblocks webhook signature");
    }

    // Process transaction status update
    const { id, status, txHash } = payload;

    await ctx.db
      .update(custodyTransactions)
      .set({
        status,
        blockchainTxHash: txHash,
        completedAt: status === "COMPLETED" ? new Date() : undefined,
      })
      .where(eq(custodyTransactions.externalId, id));

    return { success: true };
  }
}

Corporate actions automation

Webhook configuration for dividend payments

// kit/dapp/src/integrations/corporate-actions/dividend-distributor.ts
export class DividendDistributor {
  async scheduleDividendPayment(params: {
    tokenAddress: Address;
    recordDate: Date;
    paymentDate: Date;
    amountPerToken: bigint;
    paymentCurrency: "USDC" | "USDT";
  }) {
    // 1. Capture token holders snapshot at record date
    const snapshot = await this.captureHolderSnapshot(
      params.tokenAddress,
      params.recordDate
    );

    // 2. Calculate dividend amounts
    const distributions = snapshot.map((holder) => ({
      address: holder.address,
      dividendAmount:
        (holder.balance * params.amountPerToken) / BigInt(10 ** 18),
    }));

    // 3. Schedule payment execution
    await ctx.db.insert(scheduledPayments).values({
      tokenAddress: params.tokenAddress,
      paymentDate: params.paymentDate,
      distributions: JSON.stringify(distributions),
      status: "scheduled",
    });

    // 4. Set up cron job for payment execution
    await scheduleJob(params.paymentDate, async () => {
      await this.executePayments(
        params.tokenAddress,
        distributions,
        params.paymentCurrency
      );
    });

    return { distributionCount: distributions.length };
  }

  private async executePayments(
    tokenAddress: Address,
    distributions: Array<{ address: Address; dividendAmount: bigint }>,
    currency: string
  ) {
    const currencyContract = currency === "USDC" ? USDC_ADDRESS : USDT_ADDRESS;

    // Execute batch payment
    for (const dist of distributions) {
      await viem.writeContract({
        address: currencyContract,
        abi: ERC20_ABI,
        functionName: "transfer",
        args: [dist.address, dist.dividendAmount],
      });

      // Record payment in database
      await ctx.db.insert(dividendPayments).values({
        tokenAddress,
        recipientAddress: dist.address,
        amount: dist.dividendAmount.toString(),
        currency,
        paidAt: new Date(),
      });
    }
  }
}

Registrar integration for corporate action notifications

// kit/dapp/src/integrations/corporate-actions/registrar-adapter.ts
export class RegistrarAdapter {
  async notifyCorporateAction(params: {
    tokenAddress: Address;
    actionType: "dividend" | "stock_split" | "rights_issue" | "redemption";
    recordDate: Date;
    paymentDate?: Date;
    ratio?: string;
  }) {
    // Generate corporate action notification
    const notification = {
      isin: await this.getISIN(params.tokenAddress),
      actionType: params.actionType,
      recordDate: params.recordDate.toISOString(),
      paymentDate: params.paymentDate?.toISOString(),
      ratio: params.ratio,
      timestamp: new Date().toISOString(),
    };

    // Send to registrar via API
    await axios.post(process.env.REGISTRAR_API_URL!, notification, {
      headers: {
        Authorization: `Bearer ${process.env.REGISTRAR_API_KEY}`,
        "Content-Type": "application/json",
      },
    });

    // Store notification record
    await ctx.db.insert(corporateActionNotifications).values({
      tokenAddress: params.tokenAddress,
      actionType: params.actionType,
      notifiedAt: new Date(),
      externalRef: notification.timestamp,
    });
  }
}

Integration best practices

Webhook security

Signature verification:

Always verify webhook signatures to prevent spoofing:

function verifyWebhookSignature(
  payload: string,
  signature: string,
  secret: string
): boolean {
  const expectedSignature = crypto
    .createHmac("sha256", secret)
    .update(payload)
    .digest("hex");

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expectedSignature)
  );
}

IP whitelisting:

Restrict webhook endpoints to known provider IPs:

// In NGINX ingress
nginx.ingress.kubernetes.io/whitelist-source-range: "203.0.113.0/24,198.51.100.0/24"

Idempotency:

Use unique transaction IDs to prevent duplicate processing:

async function handleWebhook(payload: { transactionId: string /* ... */ }) {
  // Check if already processed
  const existing = await ctx.db
    .select()
    .from(processedWebhooks)
    .where(eq(processedWebhooks.transactionId, payload.transactionId))
    .limit(1);

  if (existing.length > 0) {
    return { status: "already_processed" };
  }

  // Process webhook
  await processPayment(payload);

  // Record as processed
  await ctx.db.insert(processedWebhooks).values({
    transactionId: payload.transactionId,
    processedAt: new Date(),
  });

  return { status: "processed" };
}

Error handling and retries

Circuit breaker pattern:

import { CircuitBreaker } from "opossum";

const breaker = new CircuitBreaker(kycProvider.submitApplication, {
  timeout: 30000, // 30s
  errorThresholdPercentage: 50,
  resetTimeout: 60000, // 1 minute
});

breaker.on("open", () => {
  console.error("Circuit breaker opened - KYC provider unavailable");
  // Switch to fallback or manual processing
});

// Use circuit breaker
await breaker.fire(applicationData);

Exponential backoff:

async function retryWithBackoff<T>(
  fn: () => Promise<T>,
  maxRetries: number = 3,
  baseDelay: number = 1000
): Promise<T> {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      return await fn();
    } catch (error) {
      if (attempt === maxRetries - 1) {
        throw error;
      }

      const delay = baseDelay * Math.pow(2, attempt);
      await sleep(delay);
    }
  }

  throw new Error("Max retries exceeded");
}

Monitoring integrations

Track integration health:

Use observability dashboard to monitor:

  1. Success rate - Track successful vs. failed integration calls
  2. Response time - Monitor p50/p95/p99 latency for external APIs
  3. Error types - Categorize failures (timeout, 4xx, 5xx, validation)
  4. Webhook delivery - Track webhook receipt and processing time

Alert configuration:

# Grafana alert for integration failures
- alert: HighIntegrationFailureRate
  expr:
    rate(integration_errors_total[5m]) / rate(integration_requests_total[5m]) >
    0.1
  for: 10m
  labels:
    severity: warning
  annotations:
    summary: "High failure rate for {{ $labels.integration }}"
    description: "Integration {{ $labels.integration }} failure rate >10% for 10 minutes"

Testing integrations

Sandbox environments

Use provider sandbox environments for testing:

// Environment-aware configuration
const SUMSUB_URL =
  process.env.NODE_ENV === "production"
    ? "https://api.sumsub.com"
    : "https://test-api.sumsub.com";

const FIREBLOCKS_URL =
  process.env.NODE_ENV === "production"
    ? "https://api.fireblocks.io"
    : "https://sandbox-api.fireblocks.io";

Mock adapters for local development

// kit/dapp/src/integrations/kyc/mock-kyc-adapter.ts
export class MockKYCAdapter implements IKYCProvider {
  async submitApplication(params: any) {
    // Simulate processing delay
    await sleep(2000);

    return {
      verificationId: `mock-${generateId()}`,
    };
  }

  async getVerificationStatus(verificationId: string) {
    // Auto-approve for testing
    return {
      status: "approved" as const,
      completedAt: new Date(),
    };
  }

  async handleWebhook(payload: any, signature: string) {
    return {
      verificationId: payload.id,
      status: "approved",
      investorId: payload.externalUserId,
    };
  }
}

// Use in development
export const kycAdapter =
  process.env.NODE_ENV === "development"
    ? new MockKYCAdapter()
    : new SumsubAdapter();

Troubleshooting

Webhook not receiving:

  • Verify webhook URL is publicly accessible
  • Check firewall/ingress whitelist includes provider IPs
  • Confirm webhook is configured in provider dashboard
  • Test with webhook testing tools (webhook.site, requestbin)

Signature verification failing:

  • Ensure secret key matches provider configuration
  • Check payload encoding (some providers sign raw bytes, others sign JSON string)
  • Verify timestamp tolerance for time-based signatures
  • Log both calculated and received signatures for debugging

Integration timeout:

  • Check provider status page for outages
  • Verify network connectivity from ATK to provider
  • Increase timeout configuration
  • Implement circuit breaker to prevent cascading failures

For additional help, see Production operations or Observability monitoring.

Next steps

  • Compliance configuration - Configure modules to work with KYC claims: Compliance configuration
  • Payment rails - Implement stablecoin settlement: DvP settlement
  • Observability - Monitor integration health: Observability & monitoring
  • API reference - Explore ORPC procedures: API reference
llms-full.txt

On this page

Integration architectureIntegration layersIntegration principlesPayment rails integrationSupported payment railsStablecoin settlement integrationRTGS/SWIFT integrationSEPA Direct Debit integrationKYC provider integrationKYC provider adapter patternSumsub integration exampleAML screening integrationExternal identity provider integrationOAuth 2.0 / OpenID Connect integrationSAML 2.0 integrationThird-party custody integrationFireblocks integrationCorporate actions automationWebhook configuration for dividend paymentsRegistrar integration for corporate action notificationsIntegration best practicesWebhook securityError handling and retriesMonitoring integrationsTesting integrationsSandbox environmentsMock adapters for local developmentTroubleshootingNext steps