TL;DR
Braintree APIs process payments from credit cards, PayPal, Venmo, and digital wallets. Use a server-side SDK (Node.js, Python, Ruby, and more), generate client tokens for the frontend, and handle transactions, refunds, subscriptions, and webhooks. Test webhook payloads and sandbox flows with Apidog before going live.
Introduction
Braintree processes billions in payments annually and is used by companies including Uber, Airbnb, and GitHub. It supports credit cards, PayPal, Venmo, Apple Pay, Google Pay, and ACH transfers.
Payment integrations need stricter handling than typical APIs. A bad catalog response is inconvenient; a payment bug can charge customers incorrectly, fail orders, or damage trust.
Braintree provides two frontend paths:
- Drop-in UI: a pre-built payment form.
- Custom UI: full control over the checkout experience.
Both approaches use the same server-side APIs for payment processing. This guide focuses on what your backend does after the customer clicks Pay.
💡 Apidog can help you test webhook handlers and validate payment responses. Mock Braintree webhook requests locally so your code handles successful payments, failures, disputes, and subscription events before real transactions are involved.
Test Braintree webhooks with Apidog - free
Setting up Braintree
Create a Braintree account
Go to braintreepayments.com (Braintree is now PayPal Enterprise Payments) and create a sandbox account. You will receive credentials such as:
- Merchant ID:
abc123xyz - Public Key:
def456... - Private Key:
ghi789...
Keep these values out of source control. Treat the private key like a password and load all credentials from environment variables.
Install the SDK
Braintree provides server-side SDKs for multiple languages.
Node.js
npm install braintree
Python
pip install braintree
Ruby
gem install braintree
Initialize the Node.js gateway once during application startup:
const braintree = require('braintree')
const gateway = new braintree.BraintreeGateway({
environment: braintree.Environment.Sandbox,
merchantId: process.env.BRAINTREE_MERCHANT_ID,
publicKey: process.env.BRAINTREE_PUBLIC_KEY,
privateKey: process.env.BRAINTREE_PRIVATE_KEY
})
Use braintree.Environment.Sandbox while developing and switch to the production environment only after validating the full checkout flow.
Generate a client token
Before rendering a payment form, generate a client token on your server. The frontend uses this token to initialize Braintree Drop-in UI or a custom integration.
app.get('/checkout/token', async (req, res) => {
const clientToken = await gateway.clientToken.generate()
res.json({
clientToken: clientToken.clientToken
})
})
Do not expose your merchant ID, public key, or private key to the browser. Only return the generated client token.
Processing payments
Payment flow
A typical Braintree checkout flow looks like this:
- The frontend collects payment details.
- Braintree returns a payment method nonce.
- The frontend sends the nonce and order details to your server.
- Your server creates a transaction with Braintree.
- Braintree returns a success or failure result.
- Your application fulfills the order or returns an actionable error.
Charge a credit card
Your backend should use the nonce from the frontend to create a sale transaction.
app.post('/checkout', async (req, res) => {
const { paymentMethodNonce, amount, orderId } = req.body
const result = await gateway.transaction.sale({
amount,
paymentMethodNonce,
orderId,
options: {
submitForSettlement: true
}
})
if (result.success) {
return res.json({
success: true,
transactionId: result.transaction.id
})
}
return res.status(400).json({
success: false,
message: result.message
})
})
Use a unique orderId for each checkout attempt to reduce the chance of duplicate transactions.
Charge a saved payment method
After an initial payment, you can save the customer and their payment method token for future charges.
// Create a customer and save the payment method.
const result = await gateway.customer.create({
firstName: 'John',
lastName: 'Doe',
email: 'john@example.com',
paymentMethodNonce: nonce
})
// Store this token in your database, associated with the customer.
const paymentMethodToken = result.customer.paymentMethods[0].token
// Charge the saved payment method later.
await gateway.transaction.sale({
amount: '49.99',
paymentMethodToken,
options: {
submitForSettlement: true
}
})
Store the payment method token, not raw card details.
Process PayPal transactions
Braintree processes PayPal using the same transaction API. The frontend obtains a PayPal nonce, then your server creates the sale.
const result = await gateway.transaction.sale({
amount: '99.00',
paymentMethodNonce: paypalNonce,
orderId: 'ORDER-123',
options: {
submitForSettlement: true
}
})
Refunds and voids
Use a refund after a transaction has settled. Use a void before settlement to stop an authorized or submitted transaction.
Full refund
const result = await gateway.transaction.refund('transaction_id')
if (result.success) {
console.log('Refunded:', result.transaction.id)
}
Partial refund
const result = await gateway.transaction.refund('transaction_id', '50.00')
if (result.success) {
console.log('Partial refund processed')
}
Void a transaction
A void stops a transaction before it settles.
const result = await gateway.transaction.void('transaction_id')
if (result.success) {
console.log('Transaction voided')
}
Transaction status flow
authorized → submitted_for_settlement → settled
↓
voided
settled → refunded
Before issuing a refund or void, check the current transaction status and use the operation that matches that state.
Subscriptions and recurring billing
Braintree supports recurring billing through subscription plans and payment method tokens.
Create a plan
Create a plan in the Braintree control panel or via API:
const result = await gateway.plan.create({
id: 'monthly-premium',
name: 'Monthly Premium',
billingFrequency: 1,
currencyIsoCode: 'USD',
price: '29.99'
})
Create a subscription
Create a subscription using a stored payment method token and a plan ID.
const result = await gateway.subscription.create({
paymentMethodToken,
planId: 'monthly-premium',
firstBillingDate: new Date()
})
if (result.success) {
console.log('Subscription created:', result.subscription.id)
}
Cancel a subscription
const result = await gateway.subscription.cancel('subscription_id')
if (result.success) {
console.log('Subscription cancelled')
}
Update a subscription
const result = await gateway.subscription.update('subscription_id', {
planId: 'annual-premium',
price: '299.99'
})
Use webhooks to track recurring billing outcomes rather than assuming each scheduled charge succeeds.
Webhooks for payment events
Webhooks notify your application when payment events occur. They are especially important for subscription billing, disputes, settlements, and refunds.
Create a webhook endpoint
Verify and parse every incoming webhook before handling it.
app.post('/webhooks/braintree', (req, res) => {
const signature = req.body.bt_signature
const payload = req.body.bt_payload
gateway.webhookNotification.parse(
signature,
payload,
(err, webhookNotification) => {
if (err) {
return res.status(400).send('Invalid webhook')
}
switch (webhookNotification.kind) {
case 'subscription_charged_successfully':
handleSuccessfulCharge(webhookNotification.subscription)
break
case 'subscription_charged_unsuccessfully':
handleFailedCharge(webhookNotification.subscription)
break
case 'dispute_opened':
handleDispute(webhookNotification.dispute)
break
case 'transaction_settled':
handleSettledTransaction(webhookNotification.transaction)
break
}
return res.status(200).send('OK')
}
)
})
Keep webhook handling focused and idempotent. A webhook may be delivered more than once, so avoid processing the same transaction or subscription event twice.
Register the webhook in Braintree
In the Braintree control panel:
- Go to Settings → Webhooks.
- Add your public webhook endpoint URL.
- Configure your application to receive and verify the webhook payload.
For local development, use a tunneling service such as ngrok to make your local endpoint reachable.
Testing with Apidog
Payment APIs need thorough testing. Do not rely on production data to validate checkout logic, webhook handling, or error flows.
1. Mock webhook payloads
Instead of waiting for Braintree to send a webhook, send a test request to your local endpoint.
{
"bt_signature": "test_signature",
"bt_payload": "eyJraW5kIjoidHJhbnNhY3Rpb25fc2V0dGxlZCIsInRyYW5zYWN0aW9uIjp7ImlkIjoiYWJjMTIzIiwiYW1vdW50IjoiNDkuOTkiLCJzdGF0dXMiOiJzZXR0bGVkIn19"
}
Use these tests to verify that your endpoint:
- Rejects invalid signatures.
- Handles the expected webhook kinds.
- Returns a
200 OKresponse for valid events. - Updates your database or logs the transaction ID as expected.
2. Separate sandbox and production environments
Keep credentials and environment settings separate.
# Sandbox
BRAINTREE_MERCHANT_ID: sandbox_merchant
BRAINTREE_PUBLIC_KEY: sandbox_public
BRAINTREE_PRIVATE_KEY: sandbox_private
BRAINTREE_ENVIRONMENT: sandbox
# Production
BRAINTREE_MERCHANT_ID: live_merchant
BRAINTREE_PUBLIC_KEY: live_public
BRAINTREE_PRIVATE_KEY: live_private
BRAINTREE_ENVIRONMENT: production
Use sandbox credentials in local development and CI tests. Do not reuse live credentials in test collections.
3. Validate webhook responses
Add assertions to confirm that the webhook endpoint responds correctly.
pm.test('Webhook processed successfully', () => {
pm.response.to.have.status(200)
pm.response.to.have.body('OK')
})
pm.test('Transaction ID logged', () => {
// Check your logs or database.
const transactionId = pm.environment.get('last_transaction_id')
pm.expect(transactionId).to.not.be.empty
})
Test Braintree webhooks with Apidog - free
Common errors and fixes
Processor declined
Cause: The issuing bank rejected the transaction.
Fix: Show a generic customer-facing message, suggest another payment method, and log the processor response code for debugging.
if (!result.success) {
if (result.transaction.processorResponseCode === '2000') {
return res.status(400).json({
error: 'Your bank declined this transaction. Please try a different card.'
})
}
}
Gateway rejected
Cause: Braintree fraud filters blocked the transaction.
Fix: Inspect gatewayRejectionReason and handle the result appropriately.
if (result.transaction.gatewayRejectionReason === 'cvv') {
// CVV mismatch
}
if (result.transaction.gatewayRejectionReason === 'avs') {
// Address verification failed
}
if (result.transaction.gatewayRejectionReason === 'fraud') {
// Advanced fraud tools blocked it
}
Settlement failures
Cause: A transaction could not settle after authorization.
Fix: Monitor transaction_settlement_declined webhooks. Common causes include:
- The payment method expired between authorization and settlement.
- The issuer blocked the transaction.
- Insufficient funds became apparent.
Duplicate transactions
Cause: The customer clicked Pay twice or your server retried a request.
Fix: Pass a unique orderId when creating a transaction.
const result = await gateway.transaction.sale({
amount: '49.99',
paymentMethodNonce: nonce,
orderId: 'UNIQUE-ORDER-123',
options: {
submitForSettlement: true
}
})
Alternatives and comparisons
| Feature | Braintree | Stripe | PayPal |
|---|---|---|---|
| Pricing | 2.9% + 30¢ | 2.9% + 30¢ | 2.9% + 30¢ |
| PayPal support | Native | Add-on | Native |
| Subscriptions | Yes | Yes | Limited |
| International | 46 countries | 46 countries | 200+ countries |
| Fraud tools | Built-in | Built-in | Basic |
| SDK quality | Excellent | Excellent | Good |
| Payouts | Yes | Yes | Yes |
Braintree’s main advantage is native PayPal and Venmo support. If you need both card processing and PayPal, a unified Braintree integration can be simpler than combining Stripe and PayPal separately.
Real-world use cases
SaaS subscription platform
A project management tool uses Braintree for monthly subscriptions. Webhooks handle failed charges, such as expired cards or insufficient funds, by triggering email notifications. Users can update payment methods without contacting support.
Marketplace payments
A freelance platform splits payments between a platform fee and freelancer payouts. Braintree merchant accounts and sub-merchant setup handle the payment complexity.
E-commerce with PayPal
An online store offers both card payments and PayPal. Braintree’s unified API allows one integration to support both payment methods, while using the same customer object across payment types.
Conclusion
You now have the core server-side pieces for a Braintree integration:
- Configure the Braintree SDK with environment variables.
- Generate client tokens for frontend checkout.
- Create transactions using payment method nonces.
- Save payment method tokens for future charges.
- Process refunds and voids based on transaction status.
- Manage subscriptions and recurring billing.
- Verify webhook signatures before handling payment events.
- Test sandbox flows and webhook handlers with Apidog before going live.
FAQ
What’s a payment method nonce?
A nonce is a one-time token representing a payment method. The frontend generates it after a customer enters card details. Your server uses the nonce to charge the card. Nonces expire after 3 hours.
What’s the difference between authorization and settlement?
Authorization reserves funds on the card. Settlement actually charges the card. By default, Braintree auto-settles.
For pre-orders, authorize first and settle later when the order ships:
// Authorize only.
await gateway.transaction.sale({
amount: '99.00',
paymentMethodNonce: nonce,
options: {
submitForSettlement: false
}
})
// Settle later.
await gateway.transaction.submitForSettlement('transaction_id')
How do I handle currency?
Each Braintree merchant account has a default currency. Multi-currency requires multiple merchant accounts. Check with Braintree support for multi-currency setup.
What test card numbers should I use?
Braintree provides sandbox test cards:
-
4111111111111111— Visa success -
4000111111111115— Visa decline -
5555555555554444— Mastercard success -
378282246310005— Amex success
How do I handle disputes and chargebacks?
Listen for dispute_opened, dispute_won, and dispute_lost webhooks. Provide evidence through the Braintree control panel, including customer communications, delivery confirmations, and terms of service.
Can I store credit card numbers?
No. PCI compliance prohibits storing raw card numbers. Store payment method tokens instead. Braintree handles the PCI scope.
What’s 3D Secure?
3D Secure adds an extra verification step for card-not-present transactions. Braintree supports it. Enable it in the control panel and handle authentication_required responses.
const result = await gateway.transaction.sale({
amount: '100.00',
paymentMethodNonce: nonce,
threeDSecure: {
required: true
}
})
How long do refunds take?
Refunds typically process in 3–5 business days. Timing depends on the customer’s bank. You will receive a transaction_refunded webhook when the refund is complete.


Top comments (0)