> For the complete documentation index, see [llms.txt](https://developer.paddle.com/llms.txt).

# Libraries

Official Paddle server-side SDKs for Node.js, Python, Go, and PHP, plus the Paddle.js wrapper for client-side JavaScript and TypeScript.

---

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

{% card-group cols=2 %}
{% card title="Node.js" icon="nodejs" url="/sdks/libraries/node" %}
TypeScript-first, iterator-based pagination, webhook verifier.
{% /card %}
{% card title="Python" icon="python" url="/sdks/libraries/python" %}
Python 3.11+, typed operation classes, Flask and Django webhook verifier.
{% /card %}
{% card title="Go" icon="go" url="/sdks/libraries/go" %}
Typed request and response structs, webhook middleware, idiomatic Go errors.
{% /card %}
{% card title="PHP" icon="php" url="/sdks/libraries/php" %}
PHP 8.1+, typed operation classes, PSR-7 compatible webhook verifier.
{% /card %}
{% /card-group %}

## Client-side

{% card-group cols=1 %}
{% card title="Paddle.js wrapper" icon="devicon-plain:javascript" url="/sdks/libraries/paddle-js-wrapper" %}
Typed ES-module wrapper around Paddle.js for use with a JavaScript package manager.
{% /card %}
{% /card-group %}

For the untyped `<script>` tag version of Paddle.js, see [Include and initialize Paddle.js](https://developer.paddle.com/paddle-js/include-paddlejs.md).

## 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`](https://developer.paddle.com/errors/shared/forbidden.md) error.

{% code-group sync="sdk-language-preference" %}

```typescript {% title="Node.js" %}
import { Paddle, Environment } from '@paddle/paddle-node-sdk';

const paddle = new Paddle(process.env.PADDLE_API_KEY!, {
  environment: Environment.sandbox,
});
```

```python {% title="Python" %}
from os import getenv
from paddle_billing import Client, Environment, Options

paddle = Client(
    getenv('PADDLE_API_KEY'),
    options=Options(Environment.SANDBOX),
)
```

```go {% title="Go" %}
import (
    "os"
    paddle "github.com/PaddleHQ/paddle-go-sdk/v5"
)

client, err := paddle.New(
    os.Getenv("PADDLE_API_KEY"),
    paddle.WithBaseURL(paddle.SandboxBaseURL),
)
```

```php {% title="PHP" %}
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),
);
```

{% /code-group %}

### 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.

{% code-group sync="sdk-language-preference" %}

```typescript {% title="Node.js" %}
const products = paddle.products.list();

for await (const product of products) {
  console.log(product.id, product.name);
}
// Or page explicitly using `.next()` and `.hasMore`.
```

```python {% title="Python" %}
for product in paddle.products.list():
    print(product.id, product.name)
```

```go {% title="Go" %}
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
})
```

```php {% title="PHP" %}
foreach ($paddle->products->list() as $product) {
    echo $product->id . ' ' . $product->name . PHP_EOL;
}
```

{% /code-group %}

### Error handling

Failed API calls raise a typed error with an `errorCode` matching the [error reference](https://developer.paddle.com/errors/overview.md). Validation errors include a list of field-level details. Rate-limit responses (`too_many_requests`) include a retry-after hint.

{% code-group sync="sdk-language-preference" %}

```typescript {% title="Node.js" %}
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;
}
```

```python {% title="Python" %}
from paddle_billing.Exceptions.ApiError import ApiError

try:
    paddle.products.create(...)
except ApiError as error:
    if error.error_code == 'conflict':
        # Handle a conflict.
        pass
    raise
```

```go {% title="Go" %}
import (
    "errors"
    paddle "github.com/PaddleHQ/paddle-go-sdk/v5"
)

_, err := client.CreateProduct(ctx, &paddle.CreateProductRequest{
    Name: "Starter",
})
var apiErr *paddle.APIError
if errors.As(err, &apiErr) && apiErr.Code == "conflict" {
    // Handle a conflict.
}
```

```php {% title="PHP" %}
use Paddle\SDK\Exceptions\ApiError;

try {
    $paddle->products->create($operation);
} catch (ApiError $e) {
    if ($e->errorCode === 'conflict') {
        // Handle a conflict.
    }
    throw $e;
}
```

{% /code-group %}

Look up a specific error code in the [error reference](https://developer.paddle.com/errors/overview.md) 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](https://developer.paddle.com/webhooks/respond-to-webhooks.md) for how signatures work and how to configure a destination.

{% code-group sync="sdk-language-preference" %}

```typescript {% title="Node.js" %}
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,
);
```

```python {% title="Python" %}
from paddle_billing.Notifications import Secret, Verifier

integrity = Verifier().verify(request, Secret(PADDLE_WEBHOOK_SECRET))
```

```go {% title="Go" %}
verifier := paddle.NewWebhookVerifier(os.Getenv("PADDLE_WEBHOOK_SECRET"))
handler := verifier.Middleware(yourHandler)
```

```php {% title="PHP" %}
use Paddle\SDK\Notifications\Secret;
use Paddle\SDK\Notifications\Verifier;

(new Verifier())->verify($request, new Secret(getenv('PADDLE_WEBHOOK_SECRET')));
```

{% /code-group %}

### 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.