# Migrate from Triple-A

> Move from Triple-A to CoinCircuit: swap OAuth2 for an API key, update webhook signing, and map every field.

Section: Migration
Source: https://coincircuit.io/docs/guides/migrate-from-triplea/
Interactive version: https://coincircuit.io/api-reference?tab=guides&guide=migrate-from-triplea

Triple-A and CoinCircuit are both licensed crypto payment platforms — you create a payment, the customer sends crypto to an address, and your server receives a notification when it settles. The integration model is similar, but the two platforms differ significantly in **authentication** and **webhook signing**. Triple-A uses OAuth2 client credentials for every API call; CoinCircuit uses a single static API key. Triple-A signs notifications with a per-payment secret; CoinCircuit signs with a global webhook secret using HMAC-SHA256 on the raw request body.

## Concept mapping

| Triple-A | CoinCircuit | Notes |
| :--- | :--- | :--- |
| OAuth2 Bearer token | `x-api-key` header | One static key replaces the token flow entirely. |
| `client_id` + `client_secret` | API key | Generate once in the dashboard under **Developer > API Keys**. |
| `merchant_key` | — | CoinCircuit keys are already scoped to your account. |
| Payment (`type: triplea`) | Payment (checkout session) | Redirect the customer to `checkoutUrl`. |
| Invoice (`type: invoice`) | Invoice | Itemised billing with a hosted pay link. |
| Widget (`type: widget`) | Checkout session | CoinCircuit's hosted checkout embeds the same way. |
| `notify_url` | `webhookUrl` | Per-payment webhook URL. |
| `notify_secret` | Webhook secret | Set once in the dashboard; CoinCircuit signs all events with it. |
| `payment_reference` | `reference` | Unique ID returned when you create the payment. |
| `order_id` | `metadata.orderId` | Store your internal ID in `metadata` — it comes back on every event. |
| `webhook_data` | `metadata` | Any extra key-value data passed through to webhooks. |
| Payment tier (`good` / `hold` / `invalid`) | Webhook events | See event mapping in Step 4. |
| `/api/v2/payout/withdraw/...` | `POST /api/v1/payouts` | Save a recipient once, then pay out to it. |
| `sandbox: true` in request body | Sandbox base URL | Use `https://sandbox-api.coincircuit.io` with test keys. |

---

## Step 1 — Replace OAuth2 with an API key

Triple-A requires a token exchange before every session. You POST your `client_id` and `client_secret` to get a Bearer token, then send it on every subsequent request.

**Triple-A**

```http
POST https://api.triple-a.io/api/v2/oauth/token
Content-Type: application/x-www-form-urlencoded

client_id=oacid-*****&client_secret=Qi42***&grant_type=client_credentials
```

```json
{ "access_token": "1ba8***", "token_type": "bearer", "expires_in": 315359999 }
```

Every subsequent request then sends `Authorization: Bearer 1ba8***`.

**CoinCircuit**

Delete the token exchange entirely. Every request sends one static header:

```http
x-api-key: sk_live_your_key
```

Generate your key in the dashboard under **Developer > API Keys**. Test keys work against `https://sandbox-api.coincircuit.io`; live keys work against `https://api.coincircuit.io`.

---

## Step 2 — Create payments

Triple-A has three payment types in one endpoint: `triplea` (hosted redirect), `widget` (embedded), and `invoice` (invoice link). The `merchant_key` selects which merchant account receives funds, and `notify_secret` signs each notification individually.

**Triple-A**

```http
POST https://api.triple-a.io/api/v2/payment
Authorization: Bearer <token>
Content-Type: application/json

{
  "type": "triplea",
  "merchant_key": "mkey-ckfhqahxy04g6e4qs6t3f00nl",
  "order_currency": "USD",
  "order_amount": 10,
  "order_id": "TSGA267KL",
  "notify_url": "https://your-site.com/webhook",
  "notify_secret": "Cf9mx4nAvRuy5vwBY2FCtaKr",
  "payer_email": "alice@example.com",
  "success_url": "https://your-site.com/success",
  "cancel_url": "https://your-site.com/cancel",
  "webhook_data": { "order_id": "TSGA267KL" }
}
```

The response includes `hosted_url` — redirect the customer there.

**CoinCircuit**

```http
POST https://api.coincircuit.io/api/v1/payments
x-api-key: sk_live_your_key
Content-Type: application/json

{
  "title": "Order TSGA267KL",
  "description": "Your order description",
  "amount": "10.00",
  "currency": "USD",
  "customer": { "email": "alice@example.com" },
  "webhookUrl": "https://your-site.com/webhook",
  "successUrl": "https://your-site.com/success",
  "cancelUrl": "https://your-site.com/cancel",
  "metadata": { "orderId": "TSGA267KL" }
}
```

The response includes `checkoutUrl` — redirect the customer there. CoinCircuit handles exchange rate locking and crypto selection internally.

**Field mapping**

| Triple-A field | CoinCircuit field | Notes |
| :--- | :--- | :--- |
| `type` | — | Not needed; CoinCircuit has one payment type. |
| `merchant_key` | — | Your API key already scopes to your account. |
| `order_currency` | `currency` | `NGN` or `USD`. |
| `order_amount` | `amount` | Pass as a string, e.g. `"10.00"`. |
| `order_id` | `metadata.orderId` | Stored in `metadata`; returned on every webhook. |
| `notify_url` | `webhookUrl` | Per-payment webhook URL. |
| `notify_secret` | Webhook secret | Set once in the dashboard; no longer per-payment. |
| `payer_email` | `customer.email` | Required. |
| `payer_name` | `customer.firstName` + `customer.lastName` | Split into two fields. |
| `payer_phone` | `customer.phone` | Optional. |
| `success_url` | `successUrl` | Redirect on successful payment. |
| `cancel_url` | `cancelUrl` | Redirect on cancellation. |
| `webhook_data` | `metadata` | Any key-value data echoed back in webhooks. |

---

## Step 3 — Replace invoices

Triple-A's invoice type (`type: invoice`) emails the customer a hosted invoice URL. CoinCircuit invoices are line-item based.

**Triple-A**

```json
{
  "type": "invoice",
  "merchant_key": "mkey-ckfhqahxy04g6e4qs6t3f00nl",
  "order_currency": "USD",
  "order_amount": 100,
  "order_id": "INV-001",
  "invoice_desc": "Consulting services — June",
  "notify_payer": true,
  "payer_email": "client@example.com",
  "payer_name": "Alice Tan"
}
```

**CoinCircuit**

Map `invoice_desc` to `description` and break the total into `items`. The `reference` field replaces `order_id`.

```http
POST https://api.coincircuit.io/api/v1/invoices
x-api-key: sk_live_your_key
Content-Type: application/json

{
  "description": "Consulting services — June",
  "currency": "USD",
  "expiresAt": "2026-08-01T00:00:00Z",
  "reference": "INV-001",
  "customer": { "email": "client@example.com", "firstName": "Alice" },
  "items": [
    { "name": "Consulting services — June", "quantity": 1, "unitPrice": 100 }
  ]
}
```

The response includes `invoiceUrl` — share it with your customer or embed it in your own email.

---

## Step 4 — Migrate webhooks

Triple-A POSTs to your `notify_url` when a payment changes tier. Each payment carries its own `notify_secret`; if omitted, Triple-A auto-generates one and returns it in the create-payment response.

**Triple-A notification payload**

```json
{
  "event": "payment",
  "merchant_key": "mkey-ckfhqahxy04g6e4qs6t3f00nl",
  "payment_reference": "SDF-453672-PMT",
  "status": "good",
  "payment_tier": "good",
  "order_currency": "USD",
  "payment_amount": 10,
  "webhook_data": { "order_id": "TSGA267KL" }
}
```

**CoinCircuit webhook handler**

CoinCircuit uses a single global webhook secret and signs every event with HMAC-SHA256 on the raw request body, sending the result in the `x-coincircuit-signature` header.

```js
const crypto = require('crypto');

app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const sig = req.headers['x-coincircuit-signature'];
  const expected = crypto
    .createHmac('sha256', process.env.WEBHOOK_SECRET)
    .update(req.body)
    .digest('hex');

  if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(sig))) {
    return res.status(401).send('Invalid signature');
  }

  const event = JSON.parse(req.body);
  res.sendStatus(200);
});
```

> Use `express.raw()` so you verify the exact bytes CoinCircuit signed, before any JSON parsing.

**Key signing differences**

| Triple-A | CoinCircuit |
| :--- | :--- |
| Per-payment `notify_secret` | Single global webhook secret in the dashboard |
| No documented signature header | `x-coincircuit-signature` (HMAC-SHA256 hex) |
| `notify_url` per payment | `webhookUrl` per payment, or global URL in dashboard |
| `webhook_data` echoed in body | `metadata` echoed in every event |

## Payment tier mapping

| Triple-A `status` / `payment_tier` | CoinCircuit event | Meaning |
| :--- | :--- | :--- |
| `none` | — | No funds received yet; CoinCircuit omits pre-deposit events. |
| `short` | `transaction.received` | Funds detected on-chain, not yet confirmed. |
| `hold` | `transaction.confirmed` | Transaction confirmed on the blockchain. |
| `good` | `payment.completed` | Payment settled in full — fulfil the order on this event. |
| `invalid` | `payment.expired` | Payment expired or could not be completed. |
| — | `payment.partial` | Partial deposit received. |

Fulfil orders on `payment.completed` (was `good` in Triple-A). Your `order_id` from `webhook_data` is now in `metadata` and comes back on every event.

---

## Step 5 — Migrate payouts

Triple-A emails the recipient a payout form to collect their wallet address. CoinCircuit saves the recipient once and pays out directly.

**Triple-A**

```http
POST https://api.triple-a.io/api/v2/payout/withdraw/local/crypto
Authorization: Bearer <token>
Content-Type: application/json

{
  "merchant_key": "mkey-ck9e0srok0000zumg7vx3hpkh",
  "email": "alice@example.com",
  "withdraw_currency": "USDT",
  "withdraw_amount": 50,
  "order_id": "PAYOUT-001",
  "notify_url": "https://your-site.com/webhook"
}
```

Check balance first with `GET /api/v2/payout/balances`. Track status with `GET /api/v2/payout/withdraw/{payout_reference}`. Payout statuses: `new` → `confirm` → `done` (or `cancel`).

**CoinCircuit**

```http
POST https://api.coincircuit.io/api/v1/crypto-addresses
x-api-key: sk_live_your_key
Content-Type: application/json

{ "address": "0xRecipientAddress", "chain": "base", "asset": "USDT", "label": "Alice" }
```

```http
GET https://api.coincircuit.io/api/v1/payouts/fees?asset=USDT&chain=base&amount=50
```

```http
POST https://api.coincircuit.io/api/v1/payouts
x-api-key: sk_live_your_key
Content-Type: application/json

{
  "asset": "USDT",
  "chain": "base",
  "amount": "50.00",
  "recipientId": "crypto-address-id-from-above"
}
```

Listen for `payout.success` or `payout.failed` webhooks. Track status with `GET /api/v1/payouts/{id}`.

---

## Step 6 — Check payment status

**Triple-A:** `GET https://api.triple-a.io/api/v2/payment/{payment_reference}` with `Authorization: Bearer <token>`.

**CoinCircuit:**

```http
GET https://api.coincircuit.io/api/v1/payments/reference/{reference}
x-api-key: sk_live_your_key
```

The `reference` is returned when you create the payment. To list all payments:

```http
GET https://api.coincircuit.io/api/v1/payments?page=1&size=20
x-api-key: sk_live_your_key
```

---

## Step 7 — Test in the sandbox

Triple-A activates test mode with a `sandbox: true` flag in the request body. CoinCircuit uses a separate base URL with test keys instead — no flag needed.

```diff
- https://api.triple-a.io/api/v2
+ https://sandbox-api.coincircuit.io/api/v1
```

Use the **Simulate payment** button on the sandbox checkout page to trigger outcomes (full, partial, overpayment, AML failure) without moving real funds. See the [Sandbox Environment](https://coincircuit.io/guides/sandbox) guide for faucet links and testnet details.
