Braintree is PayPal's full-stack payments gateway. A single integration gives you PayPal, cards, Venmo, Apple Pay, Google Pay, ACH, and local payment methods. This guide uses the official braintree Node SDK and the modern server-side PayPal flow.
Pick the right Braintree SDK
Braintree ships two server SDKs and PayPal ships a third. Don't mix them up:
braintree (this guide): the long-standing Braintree server SDK. Uses the classic REST/XML gateway with a polished promise-based API. Production-ready, actively maintained, and what every Braintree docs example uses.
@braintree/graphql-client-node: a thin GraphQL client for the same gateway. Useful if you want to write GraphQL queries directly, but you'll re-implement a lot that the classic SDK gives you for free. Skip unless you have a reason.
@paypal/paypal-server-sdk: the PayPal RESTSDK (Checkout / Orders v2). Different product, different account, different API. Don't install it for a Braintree integration.
In Settings → Processing, link your sandbox PayPal Business account so PayPal nonces flow through the same gateway as cards.
When you're ready, repeat with a production account and swap Environment.Sandbox for Environment.Production.
2. Install
ts
pnpm add braintree
The package bundles its own TypeScript declarations, no @types/braintree needed.
3. Environment variables
ts
# .envBRAINTREE_ENVIRONMENT=sandbox # or "production"BRAINTREE_MERCHANT_ID=use_your_merchant_idBRAINTREE_PUBLIC_KEY=use_your_public_keyBRAINTREE_PRIVATE_KEY=use_your_private_key
Public and private keys are bothserver-side secrets despite the names, the word “public” here means “safe to log alongside the merchant ID”, not “safe to ship to the browser”. Keep both out of client bundles.
Your browser SDK (Drop-in, Hosted Fields, Fastlane) needs a fresh client token to talk to Braintree. Pass an optional customerId so returning customers see their vaulted payment methods:
Once the browser SDK returns a paymentMethodNonce (and, for fraud scoring, deviceData), submit a sale. Always send amount as a string with two decimals, floats lose pennies.
For recurring charges that re-use a vaulted payment method while the customer is offline, swap paymentMethodNonce for paymentMethodToken and add transactionSource: "recurring"to the sale request. Braintree's built-in Recurring Billing sets this for you; only do it manually if you wrote your own subscription engine.
02noteDaloyJS routeDaloyJS routewebhookNotification.parse() verifies the signaturethrows InvalidSignatureError on tamper
03responseDaloyJS routeBraintree401 when parse throws{ error: 'invalid signature' }
04asyncDaloyJS routeYour queueEnqueue the notification, then ack200 within ~30s
The SDK verifies the signature for you, so reject parse failures with 401, hand the notification to a background job, and ack within 30 seconds.
Braintree posts webhooks as application/x-www-form-urlencoded with two fields: bt_signature and bt_payload. Pass them to webhookNotification.parse(), which verifies the signature and rejects tampered payloads with an InvalidSignatureError. You don't hash anything yourself.
Braintree expects a 2xx within 30 seconds and retries hourly for up to 24 hours in production (3 hours in sandbox). Always do the work in a background job and return 200 fast.
Errors & result objects
The SDK doesn't throw on declined transactions, it resolves with result.success === false and details under result.message, result.transaction.processorResponseCode, and result.errors.deepErrors(). The plugin above turns the unsuccessful result into a thrown error so it lands in your problem+json mapper. For genuine network failures the SDK throws an exception directly.
Runtimes
The braintree package uses Node's http/https modules and reads cert/key files from disk on init. It runs on Node, Bun, and AWS Lambda without changes, but is not compatible with Cloudflare Workers or Vercel. On edge runtimes, hit the Braintree GraphQL API directly over fetch with HTTP Basic auth (public key + private key).
Deprecation policy
Braintree publishes a server SDK deprecation policy: major versions are supported for 3 years and you should pin a recent major inpackage.json. Stay current, old SDKs lose support for new payment methods, new fields, and security patches.