> ## Documentation Index
> Fetch the complete documentation index at: https://docs.usepaykit.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# PayPal

> PayPal provider for PayKit.

```bash theme={null}
npm install @paykit-sdk/paypal
```

## Setup

<Tabs>
  <Tab title="Environment variables">
    ```typescript theme={null}
    import { PayKit } from '@paykit-sdk/core';
    import { paypal } from '@paykit-sdk/paypal';

    export const paykit = new PayKit(paypal());
    ```

    Required env vars:

    ```bash theme={null}
    PAYPAL_CLIENT_ID=...
    PAYPAL_CLIENT_SECRET=...
    PAYPAL_SANDBOX=true
    PAYPAL_WEBHOOK_SECRET=...
    ```
  </Tab>

  <Tab title="Direct config">
    ```typescript theme={null}
    import { PayKit } from '@paykit-sdk/core';
    import { createPayPal } from '@paykit-sdk/paypal';

    export const paykit = new PayKit(
      createPayPal({
        clientId: '...',
        clientSecret: '...',
        isSandbox: true,
      }),
    );
    ```
  </Tab>
</Tabs>

## How it works

```typescript theme={null}
const checkout = await paykit.checkouts.create({
  customer: { email: 'user@example.com' },
  item_id: 'sku_abc123',
  session_type: 'one_time',
  quantity: 2,
  success_url: 'https://example.com/success',
  cancel_url: 'https://example.com/cancel',
  provider_metadata: {
    currency: 'USD',
    amount: '29.00',
    itemName: 'T-Shirt',
  },
});

// Customer approves the order via PayPal's UI, then capture it
const payment = await paykit.payments.capture(checkout.id, {
  amount: 2900,
});
```

PayPal doesn't support standalone customer management — use `payer` information within orders instead. Metadata is stored as the order's `customId`, so the combined length of all metadata keys and values must stay under 127 characters after JSON serialization.

## Webhooks

Enable these events in your PayPal dashboard:

* `CHECKOUT.ORDER.APPROVED`
* `CHECKOUT.ORDER.COMPLETED`
* `PAYMENT.CAPTURE.COMPLETED`
* `PAYMENT.CAPTURE.REFUNDED`
* `BILLING.SUBSCRIPTION.CREATED`
* `BILLING.SUBSCRIPTION.UPDATED`
* `BILLING.SUBSCRIPTION.ACTIVATED`
* `BILLING.SUBSCRIPTION.SUSPENDED`
* `BILLING.SUBSCRIPTION.CANCELLED`
* `BILLING.SUBSCRIPTION.EXPIRED`

```typescript theme={null}
const webhook = paykit.webhooks
  .setup({ webhookSecret: process.env.PAYPAL_WEBHOOK_SECRET! }) // Webhook ID from dashboard
  .on('payment.created', async event => {
    /* CHECKOUT.ORDER.APPROVED */
  })
  .on('payment.succeeded', async event => {
    /* CHECKOUT.ORDER.COMPLETED or PAYMENT.CAPTURE.COMPLETED */
  })
  .on('refund.created', async event => {
    /* PAYMENT.CAPTURE.REFUNDED */
  })
  .on('subscription.created', async event => {
    /* BILLING.SUBSCRIPTION.CREATED */
  })
  .on('subscription.updated', async event => {
    /* BILLING.SUBSCRIPTION.UPDATED / ACTIVATED / SUSPENDED */
  })
  .on('subscription.canceled', async event => {
    /* BILLING.SUBSCRIPTION.CANCELLED / EXPIRED */
  });

await webhook.handle({
  body: rawBody,
  headersAsObject: Object.fromEntries(request.headers),
  fullUrl: request.url,
});
```

### Raw PayPal events

Opt into any native PayPal event — typed against the full PayPal event catalog:

```typescript theme={null}
paykit.webhooks
  .setup({ webhookSecret: process.env.PAYPAL_WEBHOOK_SECRET! })
  .on('paypal.PAYMENT.CAPTURE.COMPLETED', async event => {
    // event.data is typed as PayPalWebhookEvent<'PAYMENT.CAPTURE.COMPLETED'>
  })
  .on('paypal.BILLING.SUBSCRIPTION.PAYMENT.FAILED', async event => {
    // event.data is typed as PayPalWebhookEvent<'BILLING.SUBSCRIPTION.PAYMENT.FAILED', PayPalSubscription>
  });
```

All available raw events:

| PayPal event | PayKit event emitted |
| - | - |
| `paypal.CHECKOUT.ORDER.APPROVED` | `payment.created` |
| `paypal.CHECKOUT.ORDER.COMPLETED` | `payment.succeeded` |
| `paypal.PAYMENT.CAPTURE.COMPLETED` | `payment.succeeded` |
| `paypal.PAYMENT.CAPTURE.REFUNDED` | `refund.created` |
| `paypal.BILLING.SUBSCRIPTION.CREATED` | `subscription.created` |
| `paypal.BILLING.SUBSCRIPTION.UPDATED` | `subscription.updated` |
| `paypal.BILLING.SUBSCRIPTION.ACTIVATED` | `subscription.updated` |
| `paypal.BILLING.SUBSCRIPTION.SUSPENDED` | `subscription.updated` |
| `paypal.BILLING.SUBSCRIPTION.CANCELLED` | `subscription.canceled` |
| `paypal.BILLING.SUBSCRIPTION.EXPIRED` | `subscription.canceled` |
| `paypal.PAYMENT.AUTHORIZATION.CREATED` | *(raw only)* |
| `paypal.PAYMENT.AUTHORIZATION.VOIDED` | *(raw only)* |
| `paypal.PAYMENT.CAPTURE.DECLINED` | *(raw only)* |
| `paypal.PAYMENT.CAPTURE.PENDING` | *(raw only)* |
| `paypal.PAYMENT.CAPTURE.REVERSED` | *(raw only)* |
| `paypal.PAYMENT.REFUND.PENDING` | *(raw only)* |
| `paypal.PAYMENT.REFUND.FAILED` | *(raw only)* |
| `paypal.BILLING.SUBSCRIPTION.PAYMENT.FAILED` | *(raw only)* |
| `paypal.CUSTOMER.DISPUTE.CREATED` | *(raw only)* |
| `paypal.CUSTOMER.DISPUTE.RESOLVED` | *(raw only)* |
| `paypal.CUSTOMER.DISPUTE.UPDATED` | *(raw only)* |

## provider\_metadata

`paykit.checkouts.create` requires `currency`, `amount`, and `itemName` in `provider_metadata` because PayPal orders need explicit line-item data:

```typescript theme={null}
// checkout.provider_metadata — currency, amount, and itemName are required
await paykit.checkouts.create({
  customer: { email: 'user@example.com' },
  item_id: 'sku_abc123',
  session_type: 'one_time',
  quantity: 2,
  success_url: 'https://example.com/success',
  cancel_url: 'https://example.com/cancel',
  provider_metadata: {
    currency: 'USD', // required
    amount: '29.00', // required — total order amount as string
    itemName: 'T-Shirt', // required — display name shown in PayPal UI
  },
});

// refunds — amount is optional (defaults to full capture amount)
await paykit.refunds.create({
  payment_id: 'order_abc123',
  amount: 1000, // in currency units (e.g. cents)
});
```

## Escape hatch

`paykit._native` returns the raw PayPal SDK client, so you can reach anything PayKit does not map yet without leaving PayKit. Providers without an official SDK return [our HTTP client](/concepts/http-client) instead.

```typescript theme={null}
import { OrdersController } from '@paypal/paypal-server-sdk';

const orders = new OrdersController(paykit._native);
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.