Introduction
Appwrite 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:Appwrite builds and maintains these templates. The source code and full endpoint reference are on GitHub: payments-with-dodo-payments and subscriptions-with-dodo-payments.
How It Works
Both templates follow the same flow:- Your app calls the function’s checkout route as a signed-in user:
/checkoutfor payments,/subscribefor subscriptions. Appwrite passes the user’s ID to the function in thex-appwrite-user-idheader. - The function creates a checkout session with the user ID in
metadata.user_id, then redirects the user to the Dodo Payments checkout. - After the payment, Dodo Payments sends a webhook to the function’s
/webhookroute. The function verifies the webhook signature, fetches the payment or subscription from the Dodo Payments API instead of trusting the payload, and grants access. - If your app passes
verifyUrl, Dodo Payments also returns the customer to the function’s/successroute. That route grants access the same way, then redirects to yoursuccessUrl. Either the webhook or the redirect is enough to grant access, and running both is safe.
Prerequisites
Before you start, you need:- An Appwrite project. The template wizard stores the function code in a Git repository, so you also connect a Git provider such as GitHub.
- A Dodo Payments product 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_. - 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.
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:
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 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.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 thex-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:
checkout.js
What Each Template Grants
Payments With Dodo Payments
The function handlespayment.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 everysubscription.* 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:
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.
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. To accept real payments:- In live mode, create the product, an API key with write access, and a webhook endpoint at
https://<function-domain>/webhook. - In the function’s environment variables, set
DODO_PAYMENTS_ENVIRONMENTtolive_modeand replace the API key, product ID, and signing secret with the live values. - Redeploy the function.
Troubleshooting
Webhook Deliveries Fail With 401
Webhook Deliveries Fail With 401
- Check that
DODO_PAYMENTS_WEBHOOK_KEYmatches 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
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_idin 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
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_idin 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
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_KEYis valid, has write access, and belongs to the mode inDODO_PAYMENTS_ENVIRONMENT. - Check that
DODO_PAYMENTS_PRODUCT_IDis a product in that same mode.
Demo Page Shows CORS Errors
Demo Page Shows CORS Errors
Add the function domain as a web platform in your Appwrite project.