DEV Community

kevin.s
kevin.s

Posted on • Edited on

Build a Merchant Crypto Launch Kit for Agencies

An agency launches crypto payments for three merchant clients.

The first client uses WooCommerce and only needs a standard checkout plugin.

The second runs a custom SaaS product and needs payment-triggered account activation.

The third wants a branded payment interface, customer status page, support timeline, and finance export.

The agency treats all three projects as the same integration.

It installs some code, confirms that an invoice can be created, and marks the work complete.

Then production problems begin.

One merchant cannot explain a paying payment to a customer.

Another receives duplicate callbacks and fulfills the same order twice.

A third has no idea who owns the API credentials or who should investigate a missed payment.

The payment integration technically exists.

The delivery system does not.

A Merchant Crypto Launch Kit gives agencies a repeatable way to scope, build, verify, document, hand off, and support crypto payment projects for merchant clients.

It is not only reusable code.

It is an agency operating system for crypto payment delivery.

This article uses OxaPay as the payment infrastructure reference, but the agency model is provider-agnostic.

This article is part of 10 Crypto Payment Products Developers Can Build for Merchants.

What the agency is selling

A weak agency offer is:

We can integrate a crypto payment gateway into your website.

That sounds like a one-time technical task.

A stronger offer is:

We launch a complete crypto payment workflow for your business, including checkout, verified payment updates, order activation, support procedures, recovery, testing, and team handoff.

The merchant is not only buying an API connection.

The merchant is buying a controlled launch.

The agency is buying repeatability.

The developer building the Launch Kit creates the reusable system behind both.

What a Launch Kit actually contains

A Merchant Crypto Launch Kit should include four categories of assets.

Delivery assets

These are used to implement the payment workflow:

  • plugin deployment guides
  • hosted invoice module
  • white-label checkout module
  • static address module
  • webhook receiver
  • payment state mapping
  • fulfillment adapters
  • Payment Information lookup
  • Payment History recovery
  • dashboard starter

Decision assets

These help the agency choose the correct architecture:

  • discovery questionnaire
  • integration decision tree
  • scope calculator
  • risk classification
  • supported-platform matrix
  • responsibility matrix

Verification assets

These prove that the implementation works:

  • acceptance-test plan
  • test data
  • duplicate-event tests
  • payment-state tests
  • fulfillment tests
  • recovery tests
  • security checklist
  • signed launch report

Handoff assets

These allow the merchant to operate the integration:

  • payment-flow overview
  • support procedures
  • escalation contacts
  • credential ownership record
  • customer-response templates
  • dashboard instructions
  • incident checklist
  • post-launch monitoring agreement

A GitHub repository may contain part of the kit.

It is not the complete product.

Where this product fits

A Crypto PaymentOps Service provides the operational foundation for an individual merchant.

The Launch Kit packages repeatable parts of that foundation so an agency can deploy them across several clients.

The difference is ownership and distribution.

PaymentOps product
-> Operates one merchant payment environment

Agency Launch Kit
-> Standardizes how several merchant environments are delivered
Enter fullscreen mode Exit fullscreen mode

The Launch Kit must answer questions that a normal integration article does not:

Which implementation path fits this client?

What is included in scope?

Who owns each system?

What evidence proves launch readiness?

What does the merchant operate after handoff?

What remains the agency's responsibility?

How are future changes deployed safely?
Enter fullscreen mode Exit fullscreen mode

The launch lifecycle

The kit should standardize seven stages.

Qualification
-> Discovery
-> Architecture
-> Implementation
-> Verification
-> Handoff
-> Post-launch operations
Enter fullscreen mode Exit fullscreen mode

Each stage produces a specific output.

Stage Required output
Qualification Client fit decision
Discovery Approved requirements
Architecture Selected integration track
Implementation Configured payment workflow
Verification Acceptance-test evidence
Handoff Merchant operations package
Post-launch Monitoring and ownership plan

A project should not move to the next stage because someone says it “looks ready.”

It should move because the required output exists.

Qualify the merchant first

Not every merchant request should become a custom crypto payment project.

The agency should first determine whether the client:

  • already sells online
  • has a defined order or billing system
  • has a real customer demand for crypto
  • knows what should happen after payment
  • has someone responsible for payment support
  • can provide an HTTPS callback endpoint or use a managed service
  • can define refund and late-payment policies
  • can identify who owns production credentials
  • can participate in acceptance testing

A client that says:

Just add a wallet address to the checkout.

may not yet understand the operational requirements.

The agency should not turn an unclear request into an unclear implementation.

The discovery questionnaire

The Launch Kit should include a structured discovery form.

Business model

What does the merchant sell?

Is the purchase one-time, renewable, prepaid, or account-based?

Does successful payment trigger immediate fulfillment?

Can fulfillment be reversed?

Does the merchant sell digital or physical products?

Does the merchant have international customers?
Enter fullscreen mode Exit fullscreen mode

Current platform

Which ecommerce, billing, CMS, or application platform is used?

Is there an existing OxaPay plugin for that platform?

Is the application custom-built?

Which backend language and database are used?

Who controls deployment?
Enter fullscreen mode Exit fullscreen mode

Checkout requirements

Can the customer leave the merchant website?

Is a branded payment interface required?

Does the merchant need asset or network selection?

Is a reusable deposit address required?

Does the checkout need localization?
Enter fullscreen mode Exit fullscreen mode

Operational requirements

Who investigates payment incidents?

Who receives payment alerts?

Does the merchant need a customer status page?

Does finance require CSV or accounting exports?

How should underpayments and late payments be handled?

What happens when payment succeeds but fulfillment fails?
Enter fullscreen mode Exit fullscreen mode

Security and ownership

Who owns the OxaPay account?

Who generates the Merchant API Key?

Where will credentials be stored?

Which agency staff can access production?

Who rotates credentials after handoff?

Who approves manual payment decisions?
Enter fullscreen mode Exit fullscreen mode

The completed discovery document becomes part of the project record.

Do not rely on meeting memory.

Choose one implementation track

The Launch Kit should provide clear delivery tracks.

Track one: Plugin deployment

Use a supported plugin when:

  • the merchant uses a compatible platform
  • standard checkout behavior is sufficient
  • no custom fulfillment layer is required
  • the merchant prefers lower implementation complexity
  • the plugin supports the required order states

Possible platform categories include ecommerce, hosting billing, memberships, digital downloads, forms, and other supported systems.

The agency still needs to provide:

  • plugin compatibility review
  • installation
  • API credential configuration
  • status mapping verification
  • test payment
  • customer checkout review
  • support procedure
  • handoff documentation
  • launch monitoring

Installing the plugin is not the whole service.

Track two: Hosted invoice

Use OxaPay Generate Invoice when:

  • fast implementation matters
  • the customer can use a hosted payment page
  • the important workflow happens after payment
  • the merchant does not need full checkout UI ownership

The flow is:

Merchant order
-> Local payment session
-> OxaPay hosted invoice
-> Customer payment
-> Signed callback
-> Merchant order update
-> Fulfillment
Enter fullscreen mode Exit fullscreen mode

This is usually the best custom-development starting point.

Track three: White-label checkout

Use a white-label payment when:

  • payment must remain inside the client interface
  • the client requires branded checkout
  • network-specific instructions are important
  • the merchant needs a custom payment status experience
  • checkout design is part of the commercial value

The agency becomes responsible for displaying:

  • payment asset
  • network
  • amount
  • payment address
  • QR code
  • expiration
  • payment state
  • late-payment warnings
  • customer instructions

White-label checkout is not only a different API endpoint.

It is a larger UX and support scope.

Track four: Static address account funding

Use static addresses for account-based deposits such as:

  • prepaid hosting balance
  • customer wallet balance
  • reseller credit
  • usage credits
  • repeated account funding

Do not use static addresses merely because the merchant wants customers to “pay anytime.”

The implementation also needs:

  • customer-to-address ownership
  • deposit deduplication
  • internal balance ledger
  • callback processing
  • address lifecycle management
  • reconciliation
  • support search
  • crediting policy

Static address projects should usually be scoped separately from ordinary checkout.

The decision matrix

Requirement Recommended track
Supported platform and standard checkout Plugin
Fast custom payment launch Hosted invoice
Branded embedded payment UX White-label
Reusable customer funding address Static address
Payment-triggered business workflows Invoice or white-label plus webhook
Historical recovery and reports Payment Information and Payment History
Contractor or partner payouts Separate payout module

The decision should be recorded in the client architecture document.

It should not remain an undocumented developer preference.

Define the project boundary

Agency projects often fail because the word “integration” hides several responsibilities.

A Launch Kit should convert the project into explicit deliverables.

Example scope:

Included:
- OxaPay hosted invoice creation
- payment-to-order mapping
- HMAC-validated webhook endpoint
- paid-order activation
- customer payment status page
- support search by order ID and track_id
- Payment History recovery
- launch testing
- merchant handoff

Not included:
- refunds
- accounting
- tax calculation
- automatic payouts
- custom treasury management
- legal review
- 24/7 operational support
Enter fullscreen mode Exit fullscreen mode

The client should approve this boundary before implementation.

Use an implementation manifest

Every client deployment should have a machine-readable manifest.

client:
  id: merchant_acme
  name: Acme Software
  platform: custom_node
  environment: production

integration:
  provider: oxapay
  mode: hosted_invoice
  payment_owner: merchant
  credential_owner: merchant
  deployment_owner: agency

payment:
  pricing_currency: USD
  allowed_assets:
    - USDT
    - LTC
  callback_endpoint_id: cb_acme_7f21
  fulfill_on:
    - paid

order:
  external_id_field: order_id
  fulfillment_adapter: license_delivery

operations:
  payment_information_refresh: true
  payment_history_backfill_minutes: 10
  customer_status_page: true
  support_search: true

handoff:
  merchant_admin: ops@acme.example
  agency_support: support@agency.example
  monitoring_days: 14
Enter fullscreen mode Exit fullscreen mode

This manifest can be used to:

  • generate configuration
  • validate required fields
  • produce handoff documents
  • build test cases
  • track differences between clients
  • audit configuration changes

Do not hide critical client configuration in developer notes or chat history.

Organize the Launch Kit repository

A useful repository structure could look like:

merchant-crypto-launch-kit/
├── discovery/
│   ├── questionnaire.md
│   ├── qualification-scorecard.md
│   └── scope-template.md
├── architecture/
│   ├── decision-tree.md
│   ├── responsibility-matrix.md
│   └── threat-model.md
├── runtime/
│   ├── invoice/
│   ├── white-label/
│   ├── static-address/
│   ├── webhook/
│   ├── recovery/
│   └── fulfillment/
├── adapters/
│   ├── woocommerce/
│   ├── whmcs/
│   ├── node/
│   └── laravel/
├── qa/
│   ├── acceptance-tests.md
│   ├── security-tests.md
│   └── launch-report-template.md
├── handoff/
│   ├── merchant-guide.md
│   ├── support-sop.md
│   ├── incident-template.md
│   └── credential-register.md
└── examples/
    ├── plugin-client/
    ├── hosted-invoice-client/
    └── white-label-client/
Enter fullscreen mode Exit fullscreen mode

This communicates that the product includes more than runtime code.

Keep merchant environments isolated

An agency may manage several clients.

Do not reuse one Merchant API Key across unrelated merchants.

Each merchant deployment should have:

  • its own OxaPay merchant configuration
  • its own public webhook endpoint identifier
  • separate encrypted credentials
  • tenant-isolated records
  • separate dashboards or filtered access
  • independent test and production environments
  • documented credential owner

A shared agency platform may route callbacks through:

/webhooks/oxapay/{endpoint_id}
Enter fullscreen mode Exit fullscreen mode

The endpoint ID identifies the merchant configuration before the request body is trusted.

It must not contain the actual API key or provide administrative access.

A practical data model

CREATE TABLE agencies (
  id UUID PRIMARY KEY,
  name TEXT NOT NULL,
  created_at TIMESTAMP NOT NULL DEFAULT NOW()
);

CREATE TABLE merchants (
  id UUID PRIMARY KEY,
  agency_id UUID NOT NULL REFERENCES agencies(id),
  name TEXT NOT NULL,
  platform TEXT NOT NULL,
  environment TEXT NOT NULL,
  webhook_endpoint_id TEXT NOT NULL UNIQUE,
  oxapay_merchant_api_key_encrypted TEXT NOT NULL,
  active BOOLEAN NOT NULL DEFAULT TRUE,
  created_at TIMESTAMP NOT NULL DEFAULT NOW()
);

CREATE TABLE deployment_manifests (
  id UUID PRIMARY KEY,
  merchant_id UUID NOT NULL REFERENCES merchants(id),
  version INTEGER NOT NULL,
  manifest JSONB NOT NULL,
  approved_by TEXT,
  approved_at TIMESTAMP,
  created_at TIMESTAMP NOT NULL DEFAULT NOW(),
  UNIQUE (merchant_id, version)
);

CREATE TABLE payment_sessions (
  id UUID PRIMARY KEY,
  merchant_id UUID NOT NULL REFERENCES merchants(id),
  provider TEXT NOT NULL DEFAULT 'oxapay',
  provider_order_id TEXT NOT NULL,
  provider_track_id TEXT,
  integration_mode TEXT NOT NULL,
  requested_amount NUMERIC(20, 8) NOT NULL,
  pricing_currency TEXT NOT NULL,
  provider_status TEXT,
  internal_status TEXT NOT NULL DEFAULT 'created',
  payment_url TEXT,
  expires_at TIMESTAMP,
  created_at TIMESTAMP NOT NULL DEFAULT NOW(),
  updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
  UNIQUE (merchant_id, provider_order_id),
  UNIQUE (provider, provider_track_id)
);

CREATE TABLE payment_events (
  id UUID PRIMARY KEY,
  merchant_id UUID NOT NULL REFERENCES merchants(id),
  payment_session_id UUID REFERENCES payment_sessions(id),
  payload_hash TEXT NOT NULL,
  provider_status TEXT,
  raw_payload JSONB NOT NULL,
  signature_valid BOOLEAN NOT NULL,
  source TEXT NOT NULL,
  received_at TIMESTAMP NOT NULL DEFAULT NOW(),
  UNIQUE (merchant_id, payload_hash)
);

CREATE TABLE fulfillment_actions (
  id UUID PRIMARY KEY,
  merchant_id UUID NOT NULL REFERENCES merchants(id),
  payment_session_id UUID NOT NULL REFERENCES payment_sessions(id),
  action_type TEXT NOT NULL,
  idempotency_key TEXT NOT NULL UNIQUE,
  status TEXT NOT NULL,
  attempt_count INTEGER NOT NULL DEFAULT 0,
  last_error TEXT,
  created_at TIMESTAMP NOT NULL DEFAULT NOW(),
  completed_at TIMESTAMP
);

CREATE TABLE launch_checks (
  id UUID PRIMARY KEY,
  merchant_id UUID NOT NULL REFERENCES merchants(id),
  check_key TEXT NOT NULL,
  status TEXT NOT NULL,
  evidence JSONB,
  checked_by TEXT,
  checked_at TIMESTAMP,
  UNIQUE (merchant_id, check_key)
);

CREATE TABLE operational_cases (
  id UUID PRIMARY KEY,
  merchant_id UUID NOT NULL REFERENCES merchants(id),
  payment_session_id UUID REFERENCES payment_sessions(id),
  case_type TEXT NOT NULL,
  severity TEXT NOT NULL,
  status TEXT NOT NULL DEFAULT 'open',
  owner_type TEXT NOT NULL,
  summary TEXT NOT NULL,
  resolution_note TEXT,
  created_at TIMESTAMP NOT NULL DEFAULT NOW(),
  resolved_at TIMESTAMP
);
Enter fullscreen mode Exit fullscreen mode

The deployment manifest and launch checks are agency-specific additions.

They are not normally part of a basic merchant integration.

Create the local payment session first

The agency implementation should create an authoritative local session before calling OxaPay.

import crypto from "node:crypto";

const OXAPAY_API = "https://api.oxapay.com/v1";

export async function createMerchantInvoice({
  merchant,
  order,
}) {
  const providerOrderId =
    `order_${merchant.id}_${order.id}`;

  const session =
    await db.paymentSession.create({
      data: {
        merchantId: merchant.id,
        provider: "oxapay",
        providerOrderId,
        integrationMode: "hosted_invoice",
        requestedAmount: order.amount,
        pricingCurrency: order.currency,
        internalStatus: "created",
      },
    });

  try {
    const merchantApiKey = await decryptSecret(
      merchant.oxapayMerchantApiKeyEncrypted,
    );

    const response = await fetch(
      `${OXAPAY_API}/payment/invoice`,
      {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          merchant_api_key: merchantApiKey,
        },
        body: JSON.stringify({
          amount: order.amount,
          currency: order.currency,
          order_id: providerOrderId,
          email: order.customerEmail,
          description: `Payment for order ${order.number}`,
          callback_url:
            `${process.env.APP_URL}/webhooks/oxapay/${merchant.webhookEndpointId}`,
          return_url:
            `${order.merchantBaseUrl}/orders/${order.id}/status`,
          lifetime: 60,
          sandbox:
            merchant.environment !== "production",
        }),
      },
    );

    const payload = await response.json();

    if (!response.ok) {
      throw new Error(
        payload?.error?.message ??
          `Invoice creation failed with ${response.status}`,
      );
    }

    const payment = payload.data;

    return db.paymentSession.update({
      where: {
        id: session.id,
      },
      data: {
        providerTrackId: String(payment.track_id),
        providerStatus: "new",
        internalStatus: "invoice_created",
        paymentUrl: payment.payment_url,
        expiresAt: payment.expired_at
          ? new Date(Number(payment.expired_at) * 1000)
          : null,
      },
    });
  } catch (error) {
    await db.paymentSession.update({
      where: {
        id: session.id,
      },
      data: {
        internalStatus: "creation_failed",
      },
    });

    throw error;
  }
}
Enter fullscreen mode Exit fullscreen mode

The agency should adapt the order adapter to the merchant platform.

The core payment module should remain consistent.

Use a standard webhook module

OxaPay signs payment callbacks with HMAC SHA-512 over the raw request body using the Merchant API Key.

import crypto from "node:crypto";
import express from "express";

const app = express();

app.post(
  "/webhooks/oxapay/:endpointId",
  express.raw({
    type: "application/json",
  }),
  async (req, res) => {
    const merchant = await db.merchant.findUnique({
      where: {
        webhookEndpointId: req.params.endpointId,
      },
    });

    if (!merchant || !merchant.active) {
      return res.status(404).send("unknown endpoint");
    }

    const rawBody = req.body;
    const receivedHmac = req.get("HMAC");

    const merchantApiKey = await decryptSecret(
      merchant.oxapayMerchantApiKeyEncrypted,
    );

    const expectedHmac = crypto
      .createHmac("sha512", merchantApiKey)
      .update(rawBody)
      .digest("hex");

    if (!safeEqualSha512(receivedHmac, expectedHmac)) {
      await recordRejectedCallback({
        merchantId: merchant.id,
        reason: "invalid_hmac",
      });

      return res.status(401).send("invalid signature");
    }

    let payload;

    try {
      payload = JSON.parse(
        rawBody.toString("utf8"),
      );
    } catch {
      return res.status(400).send("invalid json");
    }

    const payloadHash = crypto
      .createHash("sha256")
      .update(rawBody)
      .digest("hex");

    try {
      const stored = await persistEventAndOutboxOnce({
        merchant,
        payload,
        payloadHash,
      });

      if (!stored) {
        return res.status(200).send("ok");
      }

      return res.status(200).send("ok");
    } catch (error) {
      console.error(
        "Webhook persistence failed",
        error,
      );

      return res.status(500).send("failed");
    }
  },
);

function safeEqualSha512(received, expected) {
  const sha512Hex = /^[a-f0-9]{128}$/i;

  if (
    !received ||
    !expected ||
    !sha512Hex.test(received) ||
    !sha512Hex.test(expected)
  ) {
    return false;
  }

  return crypto.timingSafeEqual(
    Buffer.from(received, "hex"),
    Buffer.from(expected, "hex"),
  );
}
Enter fullscreen mode Exit fullscreen mode

persistEventAndOutboxOnce should use one database transaction:

Insert payment event
+
Insert processing job
+
Commit
Enter fullscreen mode Exit fullscreen mode

If the event already exists, return successfully without creating another job.

The callback endpoint should not call the merchant fulfillment system directly.

Standardize payment status mapping

const PAYMENT_STATUS_MAP = {
  new: "created",
  waiting: "waiting",
  paying: "confirming",
  paid: "paid",
  manual_accept: "manual_review",
  underpaid: "underpaid",
  expired: "expired",
  refunding: "refund_in_progress",
  refunded: "refunded",
};

export function mapPaymentStatus(status) {
  const normalized = String(status ?? "")
    .trim()
    .toLowerCase();

  return PAYMENT_STATUS_MAP[normalized] ?? "unknown";
}
Enter fullscreen mode Exit fullscreen mode

The merchant workflow should normally fulfill on:

paid
Enter fullscreen mode Exit fullscreen mode

not:

paying
Enter fullscreen mode Exit fullscreen mode

The agency acceptance test should explicitly verify this behavior.

Use merchant adapters

Do not copy the full webhook handler for every platform.

Build adapters behind a shared contract.

type MerchantAdapter = {
  findOrder(
    externalOrderId: string
  ): Promise<MerchantOrder | null>;

  markPaymentDetected(
    order: MerchantOrder
  ): Promise<void>;

  markPaid(
    order: MerchantOrder
  ): Promise<void>;

  fulfill(
    order: MerchantOrder,
    idempotencyKey: string
  ): Promise<FulfillmentResult>;

  verifyFulfillment(
    order: MerchantOrder
  ): Promise<boolean>;
};
Enter fullscreen mode Exit fullscreen mode

Possible adapters include:

WooCommerce
WHMCS
Laravel application
Node.js application
Digital download store
SaaS billing system
Telegram membership system
Enter fullscreen mode Exit fullscreen mode

The payment runtime stays stable.

The adapter translates provider events into platform-specific actions.

Make fulfillment idempotent

A stable fulfillment key can include:

merchant_id
+ provider_track_id
+ fulfillment_action
Enter fullscreen mode Exit fullscreen mode
async function fulfillOrderOnce({
  merchant,
  session,
  adapter,
}) {
  const idempotencyKey = [
    merchant.id,
    session.providerTrackId,
    "fulfill_order",
  ].join(":");

  const existing =
    await db.fulfillmentAction.findUnique({
      where: {
        idempotencyKey,
      },
    });

  if (existing?.status === "completed") {
    return existing;
  }

  const order = await adapter.findOrder(
    session.providerOrderId,
  );

  if (!order) {
    throw new PermanentLaunchError(
      "Merchant order not found",
    );
  }

  const action =
    existing ??
    (await db.fulfillmentAction.create({
      data: {
        merchantId: merchant.id,
        paymentSessionId: session.id,
        actionType: "fulfill_order",
        idempotencyKey,
        status: "running",
        attemptCount: 1,
      },
    }));

  try {
    await adapter.markPaid(order);

    const result = await adapter.fulfill(
      order,
      idempotencyKey,
    );

    const verified =
      await adapter.verifyFulfillment(order);

    if (!verified) {
      throw new TransientLaunchError(
        "Fulfillment outcome could not be verified",
      );
    }

    await db.fulfillmentAction.update({
      where: {
        id: action.id,
      },
      data: {
        status: "completed",
        completedAt: new Date(),
      },
    });

    return result;
  } catch (error) {
    await recordFulfillmentFailure({
      actionId: action.id,
      error,
    });

    throw error;
  }
}
Enter fullscreen mode Exit fullscreen mode

A callback duplicate should not create a second product delivery or service extension.

Add recovery by default

The Launch Kit should not offer Payment History recovery as an optional afterthought.

A production deployment should include it by default.

A scheduled task can:

Retrieve recent OxaPay payments
-> Match by track_id or order_id
-> Update local provider state
-> Detect missed callbacks
-> Detect paid but unfulfilled orders
-> Create operational cases
Enter fullscreen mode Exit fullscreen mode

OxaPay Payment History is currently available through:

GET /v1/payment
Enter fullscreen mode Exit fullscreen mode

Payment Information retrieves one payment through:

GET /v1/payment/{track_id}
Enter fullscreen mode Exit fullscreen mode

A practical schedule is:

Every 10 minutes:
- query an overlapping recent time window
- upsert provider records
- identify missing local events
- retry safe processing

Every night:
- compare paid payments with merchant fulfillment
- generate unresolved-case report
Enter fullscreen mode Exit fullscreen mode

The recovery job should identify its source:

source = payment_history_backfill
Enter fullscreen mode Exit fullscreen mode

Do not make recovered records look like original callbacks.

The agency acceptance-test matrix

Testing should cover more than one successful payment.

Test Expected result
Invoice creation Local session and provider track_id stored
Invalid HMAC Request rejected and no business state changed
paying callback Payment detected, fulfillment not started
paid callback Fulfillment queued once
Duplicate paid callback No duplicate fulfillment
Unknown track_id Operational case created
Provider timeout Local failure visible and retry policy applied
Fulfillment failure Payment remains paid and case is opened
Missed callback Payment History recovery finds payment
Expired invoice Order remains unresolved under merchant policy
Customer status page Displays correct business state
Merchant export Includes expected identifiers and amounts
Credential rotation New key works and old access is removed

Each result should have evidence.

Evidence can include:

  • screenshot
  • database record
  • event ID
  • test order
  • sanitized API response
  • log reference
  • fulfillment record

“Tested successfully” without evidence is not a professional launch report.

Generate a launch report

The kit should generate a client-specific report.

launch:
  merchant: Acme Software
  environment: production
  completed_at: 2026-07-28T09:30:00Z

integration:
  mode: hosted_invoice
  webhook_endpoint: configured
  hmac_validation: passed
  payment_history_recovery: enabled

acceptance_tests:
  invoice_creation: passed
  paying_does_not_fulfill: passed
  paid_fulfills_once: passed
  duplicate_callback: passed
  recovery_job: passed
  customer_status_page: passed

credentials:
  owner: merchant
  stored_in: managed_secret_vault
  agency_access_expires: 2026-08-11

handoff:
  merchant_admin_trained: true
  support_sop_delivered: true
  escalation_contacts_confirmed: true
Enter fullscreen mode Exit fullscreen mode

This report protects both the merchant and the agency.

Build the handoff package

The merchant should receive a package containing:

Payment-flow summary

Customer creates order
-> OxaPay invoice created
-> Customer pays
-> Payment callback verified
-> Order marked paid
-> Fulfillment begins
Enter fullscreen mode Exit fullscreen mode

Dashboard guide

Explain:

  • how to find an order
  • how to search by track_id
  • how to identify paying, paid, and expired
  • how to find a failed fulfillment
  • how to export records

Support SOP

Cover at least:

  • customer says payment was sent
  • payment is still confirming
  • invoice expired
  • payment is underpaid
  • payment is paid but order is pending
  • fulfillment failed
  • customer paid twice
  • payment cannot be found

Credential register

Record:

  • account owner
  • Merchant API Key owner
  • storage location
  • agency access
  • rotation process
  • emergency revocation process

Do not include raw credentials in the handoff document.

Responsibility matrix

Activity Merchant Agency Payment provider
OxaPay account ownership Responsible Consulted Support
Merchant API Key creation Responsible Consulted Provides system
Integration deployment Consulted Responsible Not responsible
Customer-order support Responsible Escalation support Payment evidence
Application fulfillment Accountable Implementation support Not responsible
Callback delivery Informed Monitors endpoint Responsible for sending
Recovery job Informed Responsible during contract Provides API
Refund policy Responsible Not legal owner Executes supported operations

Ownership should be agreed before launch.

Define post-launch support

Every launch should have a monitoring window.

For example:

Launch day:
Review real transaction and callback health

First 72 hours:
Daily unresolved-case review

First 14 days:
Monitor webhook errors, fulfillment failures, and support issues

After monitoring period:
Move to merchant operation or maintenance plan
Enter fullscreen mode Exit fullscreen mode

The agency contract should state:

  • monitoring duration
  • support hours
  • incident channels
  • response targets
  • included changes
  • excluded changes
  • credential-access expiry
  • maintenance price

Without this boundary, a one-time launch project can become unlimited operational support.

Version client configurations

Merchant requirements change.

A client may:

  • add another currency
  • change checkout mode
  • add a new fulfillment adapter
  • rotate credentials
  • change callback domains
  • update support ownership

Do not edit production configuration without version history.

Manifest version 1
-> Hosted invoice and email delivery

Manifest version 2
-> Add customer status page

Manifest version 3
-> Add white-label checkout
Enter fullscreen mode Exit fullscreen mode

Every change should record:

  • requester
  • approver
  • implementation date
  • test evidence
  • rollback plan

The Launch Kit should standardize change management, not only initial deployment.

Build the agency portal later

The first version can be a repository, templates, and a managed implementation process.

An agency portal becomes useful after several deployments share the same needs.

Possible portal features:

  • merchant onboarding
  • manifest management
  • encrypted credential storage
  • integration-track selection
  • launch-check progress
  • webhook-health monitoring
  • client-specific dashboards
  • support-case routing
  • report generation
  • handoff-package generation
  • configuration versioning
  • agency staff permissions

Do not build the portal before the delivery process is proven manually.

Software should automate a stable process, not hide an undefined one.

Product packaging

Plugin Launch

For merchants using supported platforms.

Includes:

  • compatibility review
  • plugin installation
  • API configuration
  • checkout test
  • order-status test
  • support guide
  • handoff
  • short monitoring period

Custom Invoice Launch

For custom stores, SaaS apps, and client portals.

Includes:

  • local payment session
  • hosted invoice
  • signed webhook processing
  • order adapter
  • Payment Information refresh
  • Payment History recovery
  • status page
  • acceptance testing

White-Label Launch

For premium branded checkout.

Includes everything in Custom Invoice Launch plus:

  • embedded payment UI
  • network instructions
  • QR and address display
  • expiry handling
  • custom payment states
  • UX testing

Agency Platform

For agencies with several merchant clients.

Includes:

  • multi-client configuration
  • tenant isolation
  • webhook monitoring
  • reusable adapters
  • launch reports
  • handoff generation
  • role-based access
  • maintenance workflow

The commercial value increases with operational responsibility, not only code quantity.

The MVP

The first version of the Launch Kit should support two tracks:

Plugin deployment
Hosted invoice deployment
Enter fullscreen mode Exit fullscreen mode

Build:

  • client qualification scorecard
  • discovery questionnaire
  • decision tree
  • implementation manifest
  • one plugin deployment guide
  • one custom invoice module
  • merchant-specific webhook endpoint
  • HMAC validation
  • payment-event storage
  • idempotent fulfillment adapter
  • Payment Information refresh
  • Payment History recovery
  • acceptance-test matrix
  • launch report
  • support SOP
  • merchant handoff template
  • 14-day monitoring plan

Do not include initially:

  • every OxaPay plugin
  • every programming framework
  • custom payout systems
  • full agency SaaS portal
  • advanced accounting
  • legal or tax automation
  • unlimited merchant customization
  • dozens of dashboard roles

Prove that the same delivery system can launch two similar clients successfully.

Common mistakes

Selling only code

Agencies need scope, testing, documentation, and support boundaries.

Rebuilding a plugin unnecessarily

Use an existing plugin when it fully satisfies the merchant workflow.

Treating every merchant the same

Hosted checkout, white-label checkout, and account funding have different risks and scopes.

Starting implementation before discovery

Undefined fulfillment and support ownership create production incidents later.

Fulfilling inside the callback request

Persist the event and execute business actions asynchronously.

Returning success before durable storage

Do not acknowledge a callback that can disappear before processing.

Testing only the happy path

Duplicate events, invalid signatures, missed callbacks, and fulfillment failures are normal production concerns.

Giving the agency permanent credential access

Define who owns credentials and when agency access should end.

Handing over without support procedures

The merchant will otherwise send every payment question back to the agency.

Offering unlimited customization

Keep the core stable and charge separately for client-specific adapters.

Security baseline

Every custom deployment should include:

  • HTTPS callbacks
  • raw-body HMAC validation
  • timing-safe comparison
  • merchant-specific credentials
  • encrypted secret storage
  • tenant isolation
  • test and production separation
  • payment-event deduplication
  • fulfillment idempotency
  • transactional outbox
  • Payment Information verification
  • Payment History recovery
  • role-based dashboard access
  • manual-action audit logs
  • credential-rotation process
  • secret masking
  • post-handoff access review
  • incident alerting

The agency should be able to prove these controls were tested.

What makes this a real developer product?

A normal integration project says:

Connect API
-> Create payment
-> Receive webhook
-> Update order
Enter fullscreen mode Exit fullscreen mode

A Merchant Crypto Launch Kit says:

Qualify merchant
-> Discover operational requirements
-> Select correct implementation track
-> Approve project boundary
-> Configure versioned deployment manifest
-> Deploy reusable runtime
-> Test payment and failure paths
-> Capture launch evidence
-> Train merchant team
-> Transfer ownership
-> Monitor production
-> Manage future changes safely
Enter fullscreen mode Exit fullscreen mode

That is the difference between performing integrations and building an agency delivery product.

Final takeaway

Agencies do not need another collection of payment snippets.

They need a repeatable system for delivering crypto payment projects without recreating decisions, code, testing, documentation, and support processes for every client.

OxaPay provides the payment primitives:

  • hosted invoices
  • white-label payments
  • static addresses
  • unique track_id references
  • signed callbacks
  • Payment Information
  • Payment History
  • plugins
  • SDKs
  • automation integrations
  • optional payout APIs

The Launch Kit provides the agency layer:

  • qualification
  • discovery
  • architecture selection
  • reusable implementation
  • merchant adapters
  • acceptance testing
  • launch evidence
  • handoff
  • responsibility assignment
  • production monitoring
  • change management

Start with one agency type.

Support two implementation tracks.

Launch one client through a plugin.

Launch another through a custom hosted invoice.

Document every decision.

Test every failure path.

Make ownership explicit.

That is how one-off crypto payment work becomes a repeatable agency product.

Which agency would you build for first: ecommerce, hosting, SaaS, digital products, or creator communities?

Related articles

References

OxaPay API

OxaPay integrations

Top comments (0)