Skip to content

Quickstart

Ship a hosted-checkout integration end-to-end in under an hour.

By the end of this guide you’ll have:

  • An API key + signing secret for your merchant.
  • A signed deep link that drops a customer into Topiic’s hosted card capture.
  • A webhook endpoint that receives checkout.completed and provisions the customer on your side.

You can run the whole flow against a sandbox merchant before touching real customer cards.

You need:

  • A Topiic merchant account with at least one published plan. Ask your Topiic contact for a sandbox merchant if you don’t have one yet.
  • A login for the Topiic portal so you can mint credentials.
  • An HTTPS endpoint on your side that can receive a POST request (use webhook.site if you don’t have one yet).
  • Node 20+, Python 3.10+, or another language that can compute HMAC-SHA256.
  1. Sign into the Topiic portal.
  2. Navigate to Settings → API keys.
  3. Click New API key, name it after your integration, then Create key.
  4. You’ll see the plaintext API key (tpk_…) and signing secret (tss_…) exactly once. Copy both into your secret store immediately. Topiic only retains hashes; if you lose them you’ll need to mint a new pair.

The API key authenticates direct REST calls. The signing secret is used to HMAC-sign deep links and to verify incoming webhook payloads.

Still in Settings → API keys, find the row for the key you just minted and:

  1. Set the Endpoint URL to where you’ll receive events.
  2. Tick the events you care about (checkout.completed, checkout.failed).
  3. Toggle Enabled on.
  4. Save webhook.

Topiic will start delivering events to that URL once any are produced. Failed deliveries retry on backoff (see Retries & replay).

In the portal, open Products → <your product> → Plans and copy the Plan ID (a UUID) of the plan you want your customers to be enrolled in. You’ll embed this in the deep link.

When a user on your side clicks “Subscribe”, your server builds a URL like:

https://pay.topiic.com/c?d=<base64url payload>&s=<base64url HMAC-SHA256 signature>

The payload is a small JSON object identifying the merchant, plan, your customer reference, and a few security primitives:

{
"akid": "<your-api-key-id>",
"mid": "<your-merchant-id>",
"plan": "<plan-id>",
"ref": "your-internal-customer-id",
"contact": { "fn": "Jane", "ln": "Smith", "em": "jane@example.com", "ph": "+61 400 000 000" },
"ret": "https://your-app.example.com/subscribed?customer=42",
"evts": ["checkout.completed", "checkout.failed"],
"nonce": "<random 16+ bytes, single-use>",
"exp": 1769472000
}
import crypto from 'node:crypto';
const secret = process.env.TOPIIC_SIGNING_SECRET; // tss_…
const payload = {
akid: process.env.TOPIIC_API_KEY_ID,
mid: process.env.TOPIIC_MERCHANT_ID,
plan: 'b6a8a5b8-…-plan-id',
ref: `cust_${internalCustomerId}`,
contact: { fn: 'Jane', ln: 'Smith', em: 'jane@example.com' },
ret: 'https://your-app.example.com/subscribed?customer=42',
evts: ['checkout.completed', 'checkout.failed'],
nonce: crypto.randomBytes(16).toString('hex'),
exp: Math.floor(Date.now() / 1000) + 60 * 30 // 30 min
};
const d = Buffer.from(JSON.stringify(payload))
.toString('base64')
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
const s = crypto.createHmac('sha256', secret).update(d).digest('base64')
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
const url = `https://pay.topiic.com/c?d=${d}&s=${s}`;

Read the deep-link signing reference for the full payload schema and validation rules.

Your server returns a 303 See Other to the URL you built. The user lands on Topiic, confirms their contact details, enters their card in the gateway-hosted form, and is redirected back to your ret URL with ?status=ok (or ?status=cancelled).

The redirect is not a guarantee that everything succeeded — it’s a UX cue. The webhook is the source of truth.

When a checkout completes, Topiic POSTs to your webhook URL:

POST /webhooks/topiic HTTP/1.1
Content-Type: application/json
Topiic-Event-Id: 4f8c2a14-…
Topiic-Idempotency-Key: 4f8c2a14-…
Topiic-Signature: t=1769472312,v1=8f2a1c…
{
"id": "4f8c2a14-…",
"type": "checkout.completed",
"createdAt": "2026-05-27T10:25:12.123Z",
"data": {
"sessionId": "",
"externalRef": "cust_42",
"memberId": "",
"paymentMethodId": "",
"subscriptionId": "",
"planId": ""
}
}

Your handler must:

  1. Verify the Topiic-Signature header with your signing secret — see Verifying webhook signatures. Reject anything that doesn’t verify.
  2. Look up the event id in your database. If you’ve already processed it, return 200 OK and stop. The same event may be delivered more than once.
  3. Provision the customer on your side — link data.externalRef (your ref from step 4) to data.memberId so future events can be matched.
  4. Return a 2xx response within 10 seconds. Any other response triggers a retry with backoff.

That’s the whole hosted-checkout loop:

flowchart LR
    A[Your server] -- signed link --> B[Topiic checkout]
    B --> C[Gateway]
    C --> D[Return URL]
    B -- webhook (signed) --> E[Your server]

Next up: