@coinsph/ramp-sdk
v1.0.0
Published
Coins Ramp SDK - On-Ramp and Off-Ramp widget for merchants
Readme
@coinsph/ramp-sdk
Coins Ramp — On-Ramp (fiat → crypto) and Off-Ramp (crypto → fiat) for merchant integration.
Two integration methods:
| Method | Description | Recommended | |--------|-------------|:-----------:| | Link | Construct the buy/sell URL based on the parameters required by the document. | Yes | | SDK | JavaScript SDK opens the widget | — |
We recommend the Link method — it works everywhere (email, SMS, QR code, any web page), requires no frontend dependencies, and is the simplest integration path.
Table of Contents
- Environments
- Prerequisites
- Authentication
- Parameters
- Method 1: Link Integration (Recommended)
- Method 2: SDK Integration
- API Reference
- FAQ
Environments
| Environment | Base URL | Usage |
|-------------|----------|-------|
| production | https://coins.ph | Live transactions |
| sandbox | https://9001.pl-qa.coinsxyz.me | Testing & development |
Prerequisites
Before integrating, obtain the following from Coins:
| Item | Description |
|------|-------------|
| apiKey | Public API key, identifies your merchant account in requests |
| merchantId | Your unique merchant ID |
| secretKey | Secret key, server-side only — used by your backend to compute signature. Never put it in frontend code. |
You also need a signing endpoint on your own backend that the client can call to obtain signature. See Authentication for the contract.
Contact Coins support to obtain credentials.
Authentication
Every Buy / Sell call needs three credentials: signature, timestamp, nonce.
Generation is split between client and server:
| Field | Generated by | How |
|-------|--------------|-----|
| timestamp | Client (frontend / link generator) | Milliseconds since epoch as a string, e.g. "1714200000000" |
| nonce | Client (frontend / link generator) | UUID v4, must be unique per request |
| signature | Your backend | HMAC-SHA256 of the business params + the client-provided timestamp and nonce, signed with secretKey, output as lowercase hex |
Flow
- Client generates a fresh
timestampandnonce. - Client calls your backend signing endpoint, sending
timestampandnonceas HTTP headers (X-Timestamp,X-Nonce) along with the business parameters. - Backend computes
signatureusingsecretKey+ the headers + the business params, and returns just thesignature. - Client attaches all three values (
signature,timestamp,nonce) to the URL (Link method) or toramp.buy()/ramp.sell()(SDK method).
secretKeylives only on your server. The browser never sees it and never computes a signature.
Signing Parameters
The fields sent to the signing endpoint differ between Buy and Sell. Use the exact field names below — these must match what Coins uses for signature verification.
Buy (ON_RAMP) signing fields:
| Signing Field | Source | Description |
|---------------|--------|-------------|
| merchantId | Your merchant ID | — |
| rampType | Fixed: ON_RAMP | — |
| country | Country code | e.g. PH |
| targetCurrency | Token symbol | e.g. USDT (maps to token in URL/SDK) |
| targetNetwork | Network | e.g. SOL (maps to network in URL/SDK) |
| targetAmount | Crypto amount | e.g. 100 (maps to amount in URL/SDK) |
| targetAddress | Wallet address | (maps to address in URL/SDK) |
| tag | Memo / Tag | Optional, only for XRP / XLM / EOS |
| timestamp | Sent as HTTP header | Milliseconds since epoch |
| nonce | Sent as HTTP header | UUID v4 |
Sell (OFF_RAMP) signing fields:
| Signing Field | Source | Description |
|---------------|--------|-------------|
| merchantId | Your merchant ID | — |
| rampType | Fixed: OFF_RAMP | — |
| country | Country code | e.g. PH |
| sourceCurrency | Token symbol | e.g. USDT (maps to token in URL/SDK) |
| sourceAmount | Crypto amount | e.g. 50 (maps to amount in URL/SDK) |
| tag | Memo / Tag | Optional, only for XRP / XLM / EOS |
| timestamp | Sent as HTTP header | Milliseconds since epoch |
| nonce | Sent as HTTP header | UUID v4 |
Note: Sell does not include
networkoraddressin the signing fields.
Parameters
Both Buy and Sell use the same parameter set. The same values feed either the URL (Link method) or the SDK call (SDK method).
| Param | Required | In Link URL | In SDK | Description |
|-------|:--------:|:-----------:|:------:|-------------|
| type | Yes | Yes | — (use ramp.buy / ramp.sell) | buy or sell |
| apiKey | Yes | Yes | In new CoinsRamp({ apiKey }) | Public API key |
| merchantId | Yes | Yes | In new CoinsRamp({ merchantId }) | Merchant ID |
| country | Yes | Yes | In new CoinsRamp({ country }) | Country code, e.g. PH |
| token | Yes | Yes | Yes | Token symbol, e.g. USDT, BTC |
| network | Yes | Yes | Yes | Network, e.g. SOL, TRC20, ERC20 |
| address | Yes | Yes | Yes | Recipient (Buy) / Sender (Sell) wallet address |
| amount | Yes | Yes | Yes | Crypto amount |
| tag | No | Yes (if used) | Yes (if used) | Memo / Tag for XRP, XLM, EOS, etc. |
| signature | Yes | Yes | Yes | Returned by your backend signing endpoint (see Authentication) |
| timestamp | Yes | Yes | Yes | Milliseconds since epoch as string, e.g. "1714200000000" — generated client-side |
| nonce | Yes | Yes | Yes | UUID v4, unique per request — generated client-side |
Method 1: Link Integration (Recommended)
The simplest way to integrate — your backend generates a signed URL, and the user opens it in a browser. No JavaScript SDK needed.
Use cases: email, SMS, QR code, <a> tags on any page, mobile WebView, anywhere a URL works.
URL Format
{BASE_URL}/ramp/widget
?type={buy|sell}
&apiKey=...
&merchantId=...
&country=...
&token=...
&network=...
&address=...
&amount=...
&tag=... (optional)
&signature=...
×tamp=...
&nonce=...{BASE_URL} is https://coins.ph (production) or https://9001.pl-qa.coinsxyz.me (sandbox). See Parameters for the full list.
Buy Link Example
https://coins.ph/ramp/widget?type=buy&apiKey=your-api-key&merchantId=2058690015133241344&country=PH&token=USDT&network=SOL&address=EKi9Z...wallet&amount=100&signature=abc123...×tamp=1714200000000&nonce=f47ac10b-58cc-4372-a567-0e02b2c3d479Sell Link Example
https://coins.ph/ramp/widget?type=sell&apiKey=your-api-key&merchantId=2058690015133241344&country=PH&token=USDT&network=SOL&address=EKi9Z...wallet&amount=50&signature=def456...×tamp=1714200000000&nonce=a1b2c3d4-e5f6-7890-abcd-ef1234567890Generating the Link
The link generator (your backend, or any service that builds the URL):
- Generates a fresh
timestamp(ms) andnonce(UUID v4). - Calls your signing endpoint with the business params, passing
timestampandnonceas HTTP headers. - Receives
signaturefrom your backend. - Builds the URL using
URLSearchParams(which handles encoding for you).
Buy Link Generator:
import { v4 as uuidv4 } from 'uuid'
async function buildBuyLink({
apiKey,
merchantId,
country,
token,
network,
address,
amount,
tag,
}) {
const timestamp = String(Date.now())
const nonce = uuidv4()
// Sign using the exact field names Coins expects for Buy (ON_RAMP)
const res = await fetch('https://your-backend.example.com/ramp/sign', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Timestamp': timestamp,
'X-Nonce': nonce,
},
body: JSON.stringify({
merchantId,
rampType: 'ON_RAMP',
country,
targetCurrency: token,
targetNetwork: network,
targetAmount: String(amount),
targetAddress: address,
...(tag ? { tag } : {}),
}),
})
const { signature } = await res.json()
const params = new URLSearchParams({
type: 'buy',
apiKey,
merchantId,
country,
token,
network,
address,
amount: String(amount),
...(tag ? { tag } : {}),
signature,
timestamp,
nonce,
})
return `https://coins.ph/ramp/widget?${params.toString()}`
}Sell Link Generator:
import { v4 as uuidv4 } from 'uuid'
async function buildSellLink({
apiKey,
merchantId,
country,
token,
network,
address,
amount,
tag,
}) {
const timestamp = String(Date.now())
const nonce = uuidv4()
// Sign using the exact field names Coins expects for Sell (OFF_RAMP)
// Note: Sell does NOT include network or address in signing fields
const res = await fetch('https://your-backend.example.com/ramp/sign', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Timestamp': timestamp,
'X-Nonce': nonce,
},
body: JSON.stringify({
merchantId,
rampType: 'OFF_RAMP',
country,
sourceCurrency: token,
sourceAmount: String(amount),
...(tag ? { tag } : {}),
}),
})
const { signature } = await res.json()
const params = new URLSearchParams({
type: 'sell',
apiKey,
merchantId,
country,
token,
network,
address,
amount: String(amount),
...(tag ? { tag } : {}),
signature,
timestamp,
nonce,
})
return `https://coins.ph/ramp/widget?${params.toString()}`
}Using the Link
HTML link:
<a href="https://coins.ph/ramp/widget?type=buy&..." target="_blank">
Buy USDT with PHP
</a>JavaScript:
const buyUrl = await fetch('/api/ramp/buy-link').then(r => r.text())
window.open(buyUrl, '_blank')Email / SMS / QR code: drop the URL in directly — it works anywhere a hyperlink works.
Method 2: SDK Integration
Use the JavaScript SDK if you need programmatic control inside a web app.
Step 1: Install
npm install @coinsph/ramp-sdk
# or
yarn add @coinsph/ramp-sdk
# or
pnpm add @coinsph/ramp-sdkStep 2: Import
import { CoinsRamp } from '@coinsph/ramp-sdk'Step 3: Initialize
const ramp = new CoinsRamp({
apiKey: 'your-api-key',
merchantId: 'your-merchant-id',
country: 'PH', // optional, default 'PH'
environment: 'sandbox', // 'production' | 'sandbox', default 'production'
})| Config Field | Type | Required | Default | Description |
|--------------|------|:--------:|---------|-------------|
| apiKey | string | Yes | — | Your public API key |
| merchantId | string | Yes | — | Your merchant ID |
| country | string | No | 'PH' | Country code |
| environment | 'production' | 'sandbox' | No | 'production' | Target environment |
Step 4: Open Widget
The frontend generates timestamp and nonce, sends them as headers to your backend signing endpoint, and receives signature back. See Authentication for the full flow.
Buy (On-Ramp):
import { v4 as uuidv4 } from 'uuid'
const timestamp = String(Date.now())
const nonce = uuidv4()
const params = {
token: 'USDT',
network: 'SOL',
address: 'EKi9Z...your-wallet-address',
amount: 100,
// tag: '...', // optional, only for XRP / XLM / EOS
}
// Sign using the exact field names Coins expects for Buy
const { signature } = await fetch('/api/ramp/sign', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Timestamp': timestamp,
'X-Nonce': nonce,
},
body: JSON.stringify({
merchantId: 'your-merchant-id',
rampType: 'ON_RAMP',
country: 'PH',
targetCurrency: params.token,
targetNetwork: params.network,
targetAmount: String(params.amount),
targetAddress: params.address,
// ...(params.tag ? { tag: params.tag } : {}),
}),
}).then(r => r.json())
ramp.buy({ ...params, signature, timestamp, nonce })Sell (Off-Ramp):
import { v4 as uuidv4 } from 'uuid'
const timestamp = String(Date.now())
const nonce = uuidv4()
const params = {
token: 'USDT',
network: 'SOL',
address: 'EKi9Z...sender-wallet-address',
amount: 50,
}
// Sign using the exact field names Coins expects for Sell
// Note: Sell does NOT include network or address in signing fields
const { signature } = await fetch('/api/ramp/sign', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Timestamp': timestamp,
'X-Nonce': nonce,
},
body: JSON.stringify({
merchantId: 'your-merchant-id',
rampType: 'OFF_RAMP',
country: 'PH',
sourceCurrency: params.token,
sourceAmount: String(params.amount),
}),
}).then(r => r.json())
ramp.sell({ ...params, signature, timestamp, nonce })Both methods open the Coins widget in a new browser tab where the user completes the flow.
Step 5: Close Widget
ramp.close()Programmatically closes the popup tab if it is still open.
Full React Example
import { useState } from 'react'
import { v4 as uuidv4 } from 'uuid'
import { CoinsRamp } from '@coinsph/ramp-sdk'
function BuyCryptoButton() {
const [ramp] = useState(
() =>
new CoinsRamp({
apiKey: process.env.NEXT_PUBLIC_COINS_API_KEY!,
merchantId: process.env.NEXT_PUBLIC_COINS_MERCHANT_ID!,
environment: 'sandbox',
}),
)
const handleBuy = async () => {
const timestamp = String(Date.now())
const nonce = uuidv4()
const params = {
token: 'USDT',
network: 'SOL',
address: 'EKi9Z...your-wallet-address',
amount: 100,
}
// Sign using the exact field names Coins expects for Buy
const { signature } = await fetch('/api/ramp/sign', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Timestamp': timestamp,
'X-Nonce': nonce,
},
body: JSON.stringify({
merchantId: process.env.NEXT_PUBLIC_COINS_MERCHANT_ID!,
rampType: 'ON_RAMP',
country: 'PH',
targetCurrency: params.token,
targetNetwork: params.network,
targetAmount: String(params.amount),
targetAddress: params.address,
}),
}).then(r => r.json())
ramp.buy({ ...params, signature, timestamp, nonce })
}
return <button onClick={handleBuy}>Buy USDT</button>
}CDN / UMD Usage
For pages without a bundler:
<script src="https://unpkg.com/uuid@latest/dist/umd/uuid.min.js"></script>
<script src="https://unpkg.com/@coinsph/ramp-sdk@latest/dist/umd/coins-ramp.js"></script>
<script>
var ramp = new CoinsRampSDK.CoinsRamp({
apiKey: 'your-api-key',
merchantId: 'your-merchant-id',
environment: 'sandbox',
})
document.getElementById('buyBtn').addEventListener('click', async function () {
var timestamp = String(Date.now())
var nonce = uuid.v4()
var params = { token: 'USDT', network: 'SOL', address: 'EKi9Z...your-wallet-address', amount: 100 }
// Sign using the exact field names Coins expects for Buy
var res = await fetch('/api/ramp/sign', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Timestamp': timestamp,
'X-Nonce': nonce,
},
body: JSON.stringify({
merchantId: 'your-merchant-id',
rampType: 'ON_RAMP',
country: 'PH',
targetCurrency: params.token,
targetNetwork: params.network,
targetAmount: String(params.amount),
targetAddress: params.address,
}),
})
var data = await res.json()
ramp.buy(Object.assign({}, params, { signature: data.signature, timestamp: timestamp, nonce: nonce }))
})
</script>API Reference
CoinsRampConfig
| Field | Type | Required | Default | Description |
|-------|------|:--------:|---------|-------------|
| apiKey | string | Yes | — | Your public API key |
| merchantId | string | Yes | — | Your merchant ID |
| country | string | No | 'PH' | Country code |
| environment | 'production' | 'sandbox' | No | 'production' | Target environment |
BuyParams / SellParams
| Field | Type | Required | Description |
|-------|------|:--------:|-------------|
| token | string | Yes | Token symbol, e.g. USDT |
| network | string | Yes | Network, e.g. SOL, TRC20 |
| address | string | Yes | Recipient (Buy) / Sender (Sell) wallet address |
| amount | number | Yes | Crypto amount |
| tag | string | No | Memo / Tag for XRP, XLM, EOS, etc. |
| signature | string | Yes | From your backend signing endpoint (see Authentication) |
| timestamp | string | Yes | Generated client-side, milliseconds since epoch |
| nonce | string | Yes | Generated client-side, UUID v4 |
Methods
| Method | Description |
|--------|-------------|
| ramp.buy(params) | Open the Buy (On-Ramp) widget |
| ramp.sell(params) | Open the Sell (Off-Ramp) widget |
| ramp.close() | Close the widget tab if still open |
FAQ
Which integration method should I choose?
Link is recommended for most cases. It needs no frontend dependency and works anywhere a URL works (email, SMS, QR, web page, WebView).
Use SDK only if you need programmatic control from a single-page app (e.g. opening the widget from a React click handler with dynamic params).
Can I generate the signature in the frontend?
No. signature requires your secretKey, which must stay on your server. The frontend only generates timestamp and nonce, sends them as headers to your backend, and receives signature back. secretKey must never reach the browser.
Why does the popup not open?
Browsers block popups not triggered by a user click. With the SDK, always call ramp.buy() / ramp.sell() directly inside a user-initiated event handler (e.g. onClick). With the Link method, use <a target="_blank"> to avoid popup-blocker issues entirely.
What does the user see if the signature is invalid?
The widget shows an error and the user cannot proceed. Common causes: business param mismatch between sign request and final URL, expired timestamp, reused nonce. Regenerate timestamp / nonce on the client, request a fresh signature, and retry.
Support
For issues, questions, or feature requests, please contact Coins support
