Prerequisites
- A Hedge Payments account (sign up at dashboard.hedgepayments.com)
- Node.js 18+ (for the examples below)
Overview
Get API Keys
Grab your secret key and webhook secret from the dashboard.
Configure Round-Up Strategy
Set how round-ups are calculated for your consumers.
Link Consumer Bank Account
Connect a bank account via CoinFlow for ACH debits.
Initiate Round-Ups on Bet Events
Send transactions to SideBet when bets are placed.
Handle Webhooks
Receive real-time updates as ACH payments settle.
Step 1: Get API Keys
Log in to the Hedge Payments dashboard and navigate to Settings → API Keys. You will need two keys:
API Key (Secret)
Used for server-to-server requests. Prefix: sk_live_ or sk_test_. Never expose this in client-side code.
Webhook Secret
Used to validate incoming webhook signatures. Prefix: whsec_. Found under Settings → Webhooks.
# Store these in your environment
export HEDGE_API_KEY=#98C379]">"sk_test_your_api_key_here"
export HEDGE_WEBHOOK_SECRET=#98C379]">"whsec_your_webhook_secret_here"Step 2: Configure Round-Up Strategy
Set your merchant's round-up strategy via the configuration endpoint. This determines how round-ups are calculated for all your consumers:
curl -X POST https://api.hedgepayments.com/api/roundup/configure \
-H #98C379]">"Authorization: Bearer sk_test_your_api_key" \
-H #98C379]">"Content-Type: application/json" \
-d '{
#98C379]">"merchantId": "merch_xyz789",
#98C379]">"strategy": "nearest-dollar",
#98C379]">"perTransactionMaxCents": 1000,
#98C379]">"dailyMaxCents": 5000
}'{
#98C379]">"merchantId": "merch_xyz789",
#98C379]">"strategy": "nearest-dollar",
#98C379]">"perTransactionMaxCents": 1000,
#98C379]">"dailyMaxCents": 5000,
#98C379]">"settlementType": "USD",
#98C379]">"updated": true
}Available strategies
nearest-dollar, nearest-5, percentage, fixed-amount, dynamic. For percentage, fixed-amount, and dynamic, include a strategyValue field (e.g., "strategyValue": 5 for 5% or 5x multiplier).
Step 3: Link Consumer Bank Account
Before initiating round-ups, consumers must link their bank account via CoinFlow. All fields are required for ACH compliance (NACHA rules):
curl -X POST https://api.hedgepayments.com/api/customer/v2/bankAccount \
-H #98C379]">"Authorization: Bearer sk_test_your_api_key" \
-H #98C379]">"Content-Type: application/json" \
-d '{
#98C379]">"firstName": "Jane",
#98C379]">"lastName": "Doe",
#98C379]">"email": "jane@example.com",
#98C379]">"routingNumber": "021000021",
#98C379]">"account_number": "123456789",
#98C379]">"address1": "123 Main St",
#98C379]">"city": "New York",
#98C379]">"state": "NY",
#98C379]">"zip": "10001"
}'{
#98C379]">"customerId": "cust_j4k5l6m7",
#98C379]">"bankAccountId": "ba_n8o9p0q1",
#98C379]">"status": "LINKED",
#98C379]">"last4": "6789"
}Important
The field names follow CoinFlow's conventions. Note account_number uses snake_case while other fields use camelCase.
Step 4: Initiate Round-Ups on Bet Events
When a consumer places a bet, send the transaction to SideBet. The round-up is calculated server-side based on your configured strategy:
curl -X POST https://api.hedgepayments.com/api/roundup/initiate \
-H #98C379]">"Authorization: Bearer sk_test_your_api_key" \
-H #98C379]">"Content-Type: application/json" \
-d '{
#98C379]">"betAmount": 2347,
#98C379]">"consumerId": "cust_j4k5l6m7",
#98C379]">"strategy": "nearest-dollar",
#98C379]">"merchantId": "merch_xyz789"
}'{
#98C379]">"id": "roundup_9f8e7d6c",
#98C379]">"roundUpCents": 53,
#98C379]">"betAmountCents": 2347,
#98C379]">"status": "pending",
#98C379]">"skipped": false,
#98C379]">"consumerId": "cust_j4k5l6m7",
#98C379]">"merchantId": "merch_xyz789"
}Step 5: Handle Webhooks
SideBet sends webhook events as each ACH payment progresses. Set up an endpoint to receive them:
| Event | When | Action |
|---|---|---|
| roundup.initiated | ACH debit submitted to bank | Update status to processing |
| roundup.completed | Funds received and settled | Credit consumer's balance |
| roundup.failed | Payment could not be processed | Log error, notify consumer |
| roundup.returned | Bank returned the debit | Reverse the credit, notify consumer |
Full Working Example
Here's a complete Node.js integration that ties all five steps together -- configure strategy, link a bank account, initiate round-ups on bet events, and handle webhooks:
import express from class="text-[class="text-[#5C6370] italic">#98C379]">'express';
import crypto from class="text-[class="text-[#5C6370] italic">#98C379]">'crypto';
const app = express();
app.use(express.json());
const API_BASE = class="text-[class="text-[#5C6370] italic">#98C379]">'class="text-[#61AFEF]">https://api-sandbox.hedgepayments.com';
const API_KEY = process.env.HEDGE_API_KEY;
const WEBHOOK_SECRET = process.env.HEDGE_WEBHOOK_SECRET;
const MERCHANT_ID = class="text-[class="text-[#5C6370] italic">#98C379]">'merch_xyz789';
const headers = {
class="text-[class="text-[#5C6370] italic">#98C379]">'Authorization': class="text-[#98C379]">`Bearer ${API_KEY}`,
class="text-[class="text-[#5C6370] italic">#98C379]">'Content-Type': class="text-[#98C379]">'application/json',
};
class="text-[#5C6370] italic">// Step class="text-[#D19A66]">2: Configure round-up strategy (run once)
async function configureStrategy() {
const res = await fetch(class="text-[class="text-[#5C6370] italic">#98C379]">`${API_BASE}/api/roundup/configure`, {
method: class="text-[class="text-[#5C6370] italic">#98C379]">'POST',
headers,
body: JSON.stringify({
merchantId: MERCHANT_ID,
strategy: class="text-[class="text-[#5C6370] italic">#98C379]">'nearest-dollar',
perTransactionMaxCents: class="text-[#D19A66]">1000,
dailyMaxCents: class="text-[#D19A66]">5000,
}),
});
return res.json();
}
class="text-[#5C6370] italic">// Step class="text-[#D19A66]">3: Link a consumer's bank account
app.post(class="text-[class="text-[#5C6370] italic">#98C379]">'/link-bank', async (req, res) => {
const response = await fetch(class="text-[class="text-[#5C6370] italic">#98C379]">`${API_BASE}/api/customer/v2/bankAccount`, {
method: class="text-[class="text-[#5C6370] italic">#98C379]">'POST',
headers,
body: JSON.stringify({
firstName: req.body.firstName,
lastName: req.body.lastName,
email: req.body.email,
routingNumber: req.body.routingNumber,
account_number: req.body.accountNumber,
address1: req.body.address1,
city: req.body.city,
state: req.body.state,
zip: req.body.zip,
}),
});
const data = await response.json();
res.json(data);
});
class="text-[#5C6370] italic">// Step class="text-[#D19A66]">4: Initiate a round-up when a bet is placed
app.post(class="text-[class="text-[#5C6370] italic">#98C379]">'/place-bet', async (req, res) => {
const { consumerId, betAmountCents } = req.body;
class="text-[#5C6370] italic">// Place the bet in your system first...
class="text-[#5C6370] italic">// Then initiate the round-up:
const response = await fetch(class="text-[class="text-[#5C6370] italic">#98C379]">`${API_BASE}/api/roundup/initiate`, {
method: class="text-[class="text-[#5C6370] italic">#98C379]">'POST',
headers,
body: JSON.stringify({
betAmount: betAmountCents,
consumerId,
strategy: class="text-[class="text-[#5C6370] italic">#98C379]">'nearest-dollar',
merchantId: MERCHANT_ID,
}),
});
const roundUp = await response.json();
res.json({
betPlaced: true,
roundUp: {
id: roundUp.id,
roundUpCents: roundUp.roundUpCents,
skipped: roundUp.skipped,
},
});
});
class="text-[#5C6370] italic">// Step class="text-[#D19A66]">5: Handle webhooks with signature validation
app.post(class="text-[class="text-[#5C6370] italic">#98C379]">'/webhooks/sidebet', (req, res) => {
class="text-[#5C6370] italic">// Validate HMAC-SHA256 signature
const signature = req.headers[class="text-[class="text-[#5C6370] italic">#98C379]">'x-hedge-signature'];
const payload = JSON.stringify(req.body);
const expected = crypto
.createHmac(class="text-[class="text-[#5C6370] italic">#98C379]">'sha256', WEBHOOK_SECRET)
.update(payload)
.digest(class="text-[class="text-[#5C6370] italic">#98C379]">'hex');
if (signature !== expected) {
return res.status(class="text-[#D19A66]">401).send(class="text-[class="text-[#5C6370] italic">#98C379]">'Invalid signature');
}
class="text-[#5C6370] italic">// Respond class="text-[#D19A66]">200 immediately
res.status(class="text-[#D19A66]">200).send(class="text-[class="text-[#5C6370] italic">#98C379]">'OK');
class="text-[#5C6370] italic">// Process the event
const { event, data } = req.body;
switch (event) {
case class="text-[class="text-[#5C6370] italic">#98C379]">'roundup.initiated':
console.log(class="text-[class="text-[#5C6370] italic">#98C379]">`Round-up initiated: ${data.roundupId}`);
break;
case class="text-[class="text-[#5C6370] italic">#98C379]">'roundup.completed':
console.log(class="text-[class="text-[#5C6370] italic">#98C379]">`Round-up settled: ${data.roundupId} — $${data.amount}`);
class="text-[#5C6370] italic">// Credit the consumer's balance
break;
case class="text-[class="text-[#5C6370] italic">#98C379]">'roundup.failed':
console.log(class="text-[class="text-[#5C6370] italic">#98C379]">`Round-up failed: ${data.roundupId}`);
break;
case class="text-[class="text-[#5C6370] italic">#98C379]">'roundup.returned':
console.log(class="text-[class="text-[#5C6370] italic">#98C379]">`Round-up returned: ${data.roundupId}`);
class="text-[#5C6370] italic">// Reverse any credit
break;
}
});
app.listen(class="text-[#D19A66]">3000, () => console.log(class="text-[class="text-[#5C6370] italic">#98C379]">'Server running on port class="text-[#D19A66]">3000'));Sandbox Testing
Use the sandbox environment to test your integration without moving real money:
Sandbox Base URL
https://api-sandbox.hedgepayments.comCoinFlow Sandbox
api-sandbox.coinflow.cashSandbox bank accounts
Any routing number and account number will work in the sandbox environment. No real bank verification occurs. ACH webhooks are simulated and fire within seconds instead of days.