Paddle publishes official SDKs for Node.js, Python, Go, and PHP, plus a typed ES-module wrapper for Paddle.js. Each SDK is hand-written, kept in lockstep with the API, and distributed on the package registry for its language.
Server-side SDKs
TypeScript-first, iterator-based pagination, webhook verifier.
Python 3.11+, typed operation classes, Flask and Django webhook verifier.
Typed request and response structs, webhook middleware, idiomatic Go errors.
PHP 8.1+, typed operation classes, PSR-7 compatible webhook verifier.
Client-side
Typed ES-module wrapper around Paddle.js for use with a JavaScript package manager.
For the untyped <script> tag version of Paddle.js, see Include and initialize Paddle.js.
Common patterns
Some behavior is the same across every server-side SDK.
Authentication
Each SDK takes an API key and an environment. API keys and client-side tokens are environment-specific: use a sandbox credential for sandbox, a live credential for production. Cross-environment credentials return a forbidden error.
import { Paddle, Environment } from '@paddle/paddle-node-sdk';
const paddle = new Paddle(process.env.PADDLE_API_KEY!, { environment: Environment.sandbox,});from os import getenvfrom paddle_billing import Client, Environment, Options
paddle = Client( getenv('PADDLE_API_KEY'), options=Options(Environment.SANDBOX),)import ( "os" paddle "github.com/PaddleHQ/paddle-go-sdk/v5")
client, err := paddle.New( os.Getenv("PADDLE_API_KEY"), paddle.WithBaseURL(paddle.SandboxBaseURL),)use Paddle\SDK\Client;use Paddle\SDK\Environment;use Paddle\SDK\Options;
$paddle = new Client( apiKey: getenv('PADDLE_API_KEY'), options: new Options(Environment::SANDBOX),);Pagination
List endpoints return collections that can be iterated across pages. Each SDK exposes pagination idiomatically for its language: async iterators in Node.js, plain iterators in Python and PHP, and an Iter callback in Go.
const products = paddle.products.list();
for await (const product of products) { console.log(product.id, product.name);}// Or page explicitly using `.next()` and `.hasMore`.for product in paddle.products.list(): print(product.id, product.name)products, err := client.ListProducts(ctx, &paddle.ListProductsRequest{})if err != nil { return err}
err = products.Iter(ctx, func(p *paddle.Product) (bool, error) { fmt.Println(p.ID, p.Name) return true, nil})foreach ($paddle->products->list() as $product) { echo $product->id . ' ' . $product->name . PHP_EOL;}Error handling
Failed API calls raise a typed error with an errorCode matching the error reference. Validation errors include a list of field-level details. Rate-limit responses (too_many_requests) include a retry-after hint.
import { ApiError } from '@paddle/paddle-node-sdk';
try { await paddle.products.create({ name: 'Starter', taxCategory: 'standard' });} catch (e) { const err = e as ApiError; if (err.code === 'conflict') { // Handle a conflict, e.g. duplicate resource. } throw e;}from paddle_billing.Exceptions.ApiError import ApiError
try: paddle.products.create(...)except ApiError as error: if error.error_code == 'conflict': # Handle a conflict. pass raiseimport ( "errors" paddle "github.com/PaddleHQ/paddle-go-sdk/v5")
_, err := client.CreateProduct(ctx, &paddle.CreateProductRequest{ Name: "Starter",})var apiErr *paddle.APIErrorif errors.As(err, &apiErr) && apiErr.Code == "conflict" { // Handle a conflict.}use Paddle\SDK\Exceptions\ApiError;
try { $paddle->products->create($operation);} catch (ApiError $e) { if ($e->errorCode === 'conflict') { // Handle a conflict. } throw $e;}Look up a specific error code in the error reference for cause and remediation.
Webhook signature verification
Every SDK ships with a webhook signature verifier that checks the Paddle-Signature header using your notification destination's secret key. See respond to webhooks for how signatures work and how to configure a destination.
import { Paddle, EventName } from '@paddle/paddle-node-sdk';
const paddle = new Paddle(process.env.PADDLE_API_KEY!);
const event = paddle.webhooks.unmarshal( requestBody, process.env.PADDLE_WEBHOOK_SECRET!, signatureHeader,);from paddle_billing.Notifications import Secret, Verifier
integrity = Verifier().verify(request, Secret(PADDLE_WEBHOOK_SECRET))verifier := paddle.NewWebhookVerifier(os.Getenv("PADDLE_WEBHOOK_SECRET"))handler := verifier.Middleware(yourHandler)use Paddle\SDK\Notifications\Secret;use Paddle\SDK\Notifications\Verifier;
(new Verifier())->verify($request, new Secret(getenv('PADDLE_WEBHOOK_SECRET')));Idempotent and retried requests
The Paddle API doesn't currently support client-supplied idempotency keys for arbitrary operations.
If an SDK call times out or fails with a network error, retry carefully. A create operation may have succeeded even if your process didn't receive the response. Before retrying a create, list or get the entity to check whether it already exists.
Versioning
SDKs follow semantic versioning. Breaking changes result in a new major version.
Languages have different conventions around what's considered a breaking change. A non-breaking change to the Paddle API, like adding optional fields or enum values, doesn't necessarily mean an SDK won't require a major version bump.
Keep your SDK version up to date so types and runtime behavior stay aligned with the latest API.