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

# Appwrite

> Accept one-time payments and subscriptions in your Appwrite project with two Appwrite Functions templates that store orders and grant subscriber access.

## Introduction

[Appwrite](https://appwrite.io) is an open-source backend platform with auth, databases, storage, and serverless functions. Appwrite publishes two Appwrite Functions templates for Dodo Payments. Each template deploys a function that opens a Dodo Payments checkout for a signed-in Appwrite user, receives Dodo Payments webhooks, and gives the user access after they pay.

The two templates sell different product types:

| Template | Product type | What the customer gets after paying |
| - | - | - |
| **Payments with Dodo Payments** | One-time | An order row in an Appwrite table that only the customer can read |
| **Subscriptions with Dodo Payments** | Subscription | The `subscriber` label on their Appwrite user, for as long as the subscription has access |

> [!NOTE]
>
> Appwrite builds and maintains these templates. The source code and full endpoint reference are on GitHub: [payments-with-dodo-payments](https://github.com/appwrite/templates/tree/main/node/payments-with-dodo-payments) and [subscriptions-with-dodo-payments](https://github.com/appwrite/templates/tree/main/node/subscriptions-with-dodo-payments).

## How It Works

Both templates follow the same flow:

1. Your app calls the function's checkout route as a signed-in user: `/checkout` for payments, `/subscribe` for subscriptions. Appwrite passes the user's ID to the function in the `x-appwrite-user-id` header.
2. The function creates a [checkout session](https://docs.dodopayments.com/developer-resources/checkout-session.md) with the user ID in `metadata.user_id`, then redirects the user to the Dodo Payments checkout.
3. After the payment, Dodo Payments sends a webhook to the function's `/webhook` route. The function verifies the webhook signature, fetches the payment or subscription from the Dodo Payments API instead of trusting the payload, and grants access.
4. If your app passes `verifyUrl`, Dodo Payments also returns the customer to the function's `/success` route. That route grants access the same way, then redirects to your `successUrl`. Either the webhook or the redirect is enough to grant access, and running both is safe.

## Prerequisites

Before you start, you need:

1. An Appwrite project. The template wizard stores the function code in a Git repository, so you also connect a Git provider such as GitHub.
2. A Dodo Payments [product](https://docs.dodopayments.com/features/products.md) in test mode: a one-time product for the Payments template, or a subscription product for the Subscriptions template. Copy its product ID, which starts with `pdt_`.
3. A test mode Dodo Payments API key with write access, which the function needs to create checkout sessions. Create one under **Developer → API Keys**. See [Authentication](https://docs.dodopayments.com/api-reference/introduction.md#authentication).

## Setting Up a Template

**1. Create the Function From the Template**

In the Appwrite Console, open your project and go to **Functions → Templates**. Search for `Dodo Payments`, select **Payments with Dodo Payments** or **Subscriptions with Dodo Payments**, and click **Create function**.

**2. Choose a Runtime**

Select a Node.js runtime. The templates need Node.js 20 or later.

**3. Set the Variables**

Fill in the function's environment variables:

| Variable | Required | Value |
| - | - | - |
| `DODO_PAYMENTS_API_KEY` | Yes | Your Dodo Payments API key |
| `DODO_PAYMENTS_WEBHOOK_KEY` | Yes | The webhook endpoint's signing secret. You create the endpoint in a later step, so enter a placeholder value for now |
| `DODO_PAYMENTS_PRODUCT_ID` | Yes | The ID of the product to sell |
| `DODO_PAYMENTS_ENVIRONMENT` | No | `test_mode` or `live_mode`. Defaults to `test_mode` |
| `APPWRITE_DATABASE_ID` | No | Payments template only. The database that stores orders. Defaults to `orders` |
| `APPWRITE_TABLE_ID` | No | Payments template only. The table that stores orders. Defaults to `orders` |

You don't create an Appwrite API key. The template sets the permission scopes the function needs, and Appwrite provides a key with those scopes on each execution.

**4. Connect a Repository and Create the Function**

Create a new repository or connect an existing one, keep the default branch settings, and click **Create**. Appwrite builds and deploys the function.

**5. Copy the Function Domain**

Open the function and go to its **Domains** tab. Copy the generated domain, which ends with `.appwrite.run`.

**6. Add a Webhook Endpoint in Dodo Payments**

In the Dodo Payments dashboard, go to **Developer → Webhooks** and click **Add endpoint**. In **Endpoint URL**, enter `https://<function-domain>/webhook`.

Under **Subscribed events**, select `payment.succeeded` for the Payments template, or the `subscription.*` events for the Subscriptions template. An endpoint with no events selected receives every event type.

Click **Create endpoint**, then copy the signing secret from the endpoint's **Overview** tab.

**7. Add the Signing Secret and Redeploy**

In the Appwrite Console, open the function's **Settings** tab and go to **Environment variables**. Set `DODO_PAYMENTS_WEBHOOK_KEY` to the signing secret, then redeploy the function. Appwrite applies variable changes only to new deployments.

Until you redeploy, the function rejects webhooks with a `401` response, and Dodo Payments [retries](https://docs.dodopayments.com/developer-resources/webhooks.md#automatic-retries) them.

**8. Test a Purchase**

Add the function domain as a web platform in your Appwrite project, so the demo page can call Appwrite without CORS errors. Then open `https://<function-domain>/` in a browser. The function serves a demo page where you register as an anonymous user and start a checkout.

Pay with the test card `4242 4242 4242 4242`, any future expiry date such as `06/32`, and CVV `123`. For more test cards and scenarios, see [Testing Process](https://docs.dodopayments.com/miscellaneous/testing-process.md).

> [!TIP]
>
> The demo page lists the paid order for the Payments template, or shows the user's subscription status for the Subscriptions template.

## Starting a Checkout From Your App

The demo page is for testing. In your own app, call the function through the Appwrite SDK while the user is signed in. Appwrite then adds the `x-appwrite-user-id` header, and the function responds with a `303` redirect to the Dodo Payments checkout.

This example uses the Appwrite Web SDK, the same way the template's demo page does:

```javascript checkout.js expandable theme={null}
import { Client, Functions, ExecutionMethod } from "appwrite";

const client = new Client()
  .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
  .setProject("<PROJECT_ID>");
const functions = new Functions(client);

const execution = await functions.createExecution({
  functionId: "<FUNCTION_ID>",
  body: JSON.stringify({
    successUrl: "https://example.com/success",
    failureUrl: "https://example.com/failure",
    verifyUrl: "https://<function-domain>/success",
    email: "jane@example.com",
    name: "Jane Doe"
  }),
  async: false,
  xpath: "/checkout", // Use "/subscribe" for the Subscriptions template
  method: ExecutionMethod.POST,
  headers: { "Content-Type": "application/json" }
});

// The function answers with a redirect to the Dodo Payments checkout
const location = execution.responseHeaders.find((header) => header.name === "location");
window.location.replace(location?.value ?? "/");
```

The request body accepts these fields:

| Field | Description |
| - | - |
| `successUrl` | Where the customer goes after checkout. When you call the function through the Appwrite SDK, the function can't detect its own domain, so pass this value. |
| `failureUrl` | Where the customer goes after a cancelled or failed checkout. The function also redirects here if the user isn't signed in or the checkout session can't be created. |
| `verifyUrl` | Optional. The function's `https://<function-domain>/success` URL. Dodo Payments returns the customer there first, so access is granted before they reach `successUrl`. Without it, only the webhook grants access. |
| `email` | Optional. Prefills the customer's email address in the checkout. Test mode sends real emails to this address. |
| `name` | Optional. Prefills the customer's name when `email` is also set. |

## What Each Template Grants

### Payments With Dodo Payments

The function handles `payment.succeeded` and ignores other events. It stores an order only when the payment's status is `succeeded` and its `product_cart` includes `DODO_PAYMENTS_PRODUCT_ID`.

On the first order, the function creates the orders database and table, with a `userId` and an `orderId` column. Each order row uses the Dodo Payments payment ID as its row ID, so a repeated webhook delivery doesn't store the order twice. Only the customer who paid can read their row, so your app can list the signed-in user's orders directly from the table.

Dodo Payments sends `payment.succeeded` for every payment in your business, including subscription renewals. A subscription payment has no `product_cart`, so the Payments template ignores it, and you can run both templates against the same Dodo Payments business.

### Subscriptions With Dodo Payments

The function handles every `subscription.*` event. Webhooks can arrive late, twice, or out of order, so the function fetches the subscription's current status and applies that, whatever the event type:

| Subscription status | Effect on the Appwrite user |
| - | - |
| `active`, `past_due` | Adds the `subscriber` label |
| `on_hold`, `paused`, `cancelled`, `expired`, `failed`, `pending` | Removes the `subscriber` label |

`past_due` is the grace period while Dodo Payments retries a failed renewal, so the customer keeps access until the subscription moves to `on_hold` or `cancelled`. The `/success` route only adds the label, because the customer can arrive there while the subscription is still `pending`. For the full lifecycle, see the [Subscription Integration Guide](https://docs.dodopayments.com/developer-resources/subscription-integration-guide.md).

To gate features, check for `subscriber` in the user's labels, or grant access to Appwrite resources with the `label:subscriber` role.

## Going Live

Test mode and live mode have separate API keys, products, and webhook endpoints.

> [!WARNING]
>
> The function redirects customers to the `successUrl`, `failureUrl`, and `verifyUrl` values your app sends. Before you go live, edit the function's `src/main.js` so that the `/checkout` handler (Payments template) or the `/subscribe` handler (Subscriptions template) accepts only URLs on your own domains. Otherwise anyone can use the function to redirect people to other sites.

To accept real payments:

1. In live mode, create the product, an API key with write access, and a webhook endpoint at `https://<function-domain>/webhook`.
2. In the function's environment variables, set `DODO_PAYMENTS_ENVIRONMENT` to `live_mode` and replace the API key, product ID, and signing secret with the live values.
3. Redeploy the function.

See [Test Mode vs Live Mode](https://docs.dodopayments.com/miscellaneous/test-mode-vs-live-mode.md).

## Troubleshooting

> **Webhook Deliveries Fail With 401**
>
> * Check that `DODO_PAYMENTS_WEBHOOK_KEY` matches the signing secret on the endpoint's **Overview** tab.
> * Check that you redeployed the function after you changed the variable.
> * Check that the secret comes from the endpoint that sends the deliveries. Test mode and live mode endpoints have different signing secrets.

> **Payment Succeeds but No Order Is Stored**
>
> * Check that the webhook endpoint subscribes to `payment.succeeded`.
> * Check that the customer bought the product in `DODO_PAYMENTS_PRODUCT_ID`.
> * Check that the checkout was created through the function. The function needs `user_id` in the payment's metadata to know which Appwrite user to credit.
> * Open the function's **Executions** tab in Appwrite for its logs, and the **Logs** tab under **Developer → Webhooks** for each delivery.

> **Subscriber Label Doesn't Change**
>
> * Check that the webhook endpoint subscribes to the `subscription.*` events.
> * Check that the checkout was created through the function, so the subscription has `user_id` in its metadata.
> * If the Appwrite user was deleted, the function logs an error and acknowledges the event without changing anything.

> **Checkout Redirects to the Failure URL**
>
> * Check that the user is signed in when your app calls the function. Without a user, Appwrite doesn't send `x-appwrite-user-id`.
> * Check that `DODO_PAYMENTS_API_KEY` is valid, has write access, and belongs to the mode in `DODO_PAYMENTS_ENVIRONMENT`.
> * Check that `DODO_PAYMENTS_PRODUCT_ID` is a product in that same mode.

> **Demo Page Shows CORS Errors**
>
> Add the function domain as a web platform in your Appwrite project.

## Related Resources

> **[Appwrite Templates](https://github.com/appwrite/templates)**
>
> Source code and endpoint reference for both Dodo Payments templates.

> **[Webhooks](https://docs.dodopayments.com/developer-resources/webhooks.md)**
>
> Endpoints, event types, signature verification, and retries.

> **[Checkout Sessions](https://docs.dodopayments.com/developer-resources/checkout-session.md)**
>
> The checkout session fields the templates set, and the return URL parameters.

> **[Subscription Integration Guide](https://docs.dodopayments.com/developer-resources/subscription-integration-guide.md)**
>
> Subscription statuses, renewals, and failed payment handling.


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