Installation

Install Paymesh, wire a provider, optionally add persistence, and mount webhook handling in a way that scales beyond a demo.

Install the core package and one provider

Start with the core client and exactly one provider.

npm install paymesh @paymesh/stripe

If your product model fits Polar better:

npm install paymesh @paymesh/polar

If you need PIX transparent charges in Brazil:

npm install paymesh @paymesh/abacatepay

If you need Dodo's catalog-driven hosted checkout flow:

npm install paymesh @paymesh/dodo

You do not need a database on day one, but most real installations should add one quickly so webhook state, customers, PIX objects, and catalog snapshots are queryable locally.

Create a shared client module

Put the client in a stable server-side module such as src/lib/paymesh.ts, src/server/paymesh.ts, or app/lib/paymesh.ts.

src/lib/paymesh.ts
import { createClient } from "paymesh";
import { stripe } from "@paymesh/stripe";

export const paymesh = createClient({
  provider: stripe({
    secret: "sk_test_123",
    webhookSecret: "whsec_123",
  }),
});
src/lib/paymesh.ts
import { createClient } from "paymesh";
import { polar } from "@paymesh/polar";

export const paymesh = createClient({
  provider: polar({
    accessToken: "polar_access_123",
    webhookSecret: "whsec_polar_123",
  }),
});
src/lib/paymesh.ts
import { createClient } from "paymesh";
import { abacatepay } from "@paymesh/abacatepay";

export const paymesh = createClient({
  provider: abacatepay({
    apiKey: process.env.ABACATEPAY_API_KEY!,
    webhookSecret: process.env.ABACATEPAY_WEBHOOK_SECRET,
  }),
});
src/lib/paymesh.ts
import { createClient } from "paymesh";
import { dodo } from "@paymesh/dodo";

export const paymesh = createClient({
  provider: dodo({
    apiKey: process.env.DODO_PAYMENTS_API_KEY!,
    webhookSecret: process.env.DODO_PAYMENTS_WEBHOOK_KEY,
    baseUrl: "https://test.dodopayments.com",
  }),
});

@paymesh/dodo is catalog-driven like Polar and AbacatePay checkout flows. You create hosted payments with productIds. For BRL payments, the Dodo hosted checkout can offer Pix, but the provider does not expose paymesh.pix.

Add a database adapter

For anything beyond a toy integration, add one database adapter.

npm install @paymesh/memory
src/lib/paymesh.ts
import { createClient } from "paymesh";
import { memory } from "@paymesh/memory";
import { stripe } from "@paymesh/stripe";

export const paymesh = createClient({
  provider: stripe({
    secret: "sk_test_123",
    webhookSecret: "whsec_123",
  }),
  database: memory({
    seed: {
      customers: [
        {
          id: "cus_seed",
          provider: "stripe",
          sandbox: true,
          email: "ada@example.com",
        },
      ],
    },
  }),
});
npm install @paymesh/postgres pg
src/lib/paymesh.ts
import { createClient } from "paymesh";
import { postgres } from "@paymesh/postgres";
import { stripe } from "@paymesh/stripe";

export const paymesh = createClient({
  provider: stripe({
    secret: "sk_test_123",
    webhookSecret: "whsec_123",
  }),
  database: postgres("postgres://postgres:postgres@localhost:5432/paymesh", {
    persistRaw: true,
  }),
});
npm install @paymesh/drizzle drizzle-orm pg
src/lib/paymesh.ts
import { createClient } from "paymesh";
import { drizzle as paymeshDrizzle } from "@paymesh/drizzle";
import { stripe } from "@paymesh/stripe";
import { drizzle } from "drizzle-orm/node-postgres";

const db = drizzle("postgres://postgres:postgres@localhost:5432/paymesh");

export const paymesh = createClient({
  provider: stripe({
    secret: "sk_test_123",
    webhookSecret: "whsec_123",
  }),
  database: paymeshDrizzle(db, {
    persistRaw: true,
  }),
});
npm install @paymesh/prisma @prisma/client
src/lib/paymesh.ts
import { createClient } from "paymesh";
import { prisma as paymeshPrisma } from "@paymesh/prisma";
import { PrismaClient } from "@prisma/client";
import { stripe } from "@paymesh/stripe";

const db = new PrismaClient();

export const paymesh = createClient({
  provider: stripe({
    secret: "sk_test_123",
    webhookSecret: "whsec_123",
  }),
  database: paymeshPrisma(db, {
    persistRaw: true,
  }),
});

persistRaw: true stores provider payloads alongside normalized data. Use it when you need reconciliation, replay analysis, or provider-specific debugging. Leave it off when normalized records are enough.

Use @paymesh/memory only for tests, CI, demos, and local examples. For production persistence, choose Postgres, Drizzle, or Prisma.

Mount webhook handling

Your provider package is only half of the installation story. You also need to accept webhook traffic and dispatch normalized hooks.

app/api/webhooks/paymesh/route.ts
import { Webhooks } from "@paymesh/next";
import { paymesh } from "@/lib/paymesh";

export const POST = Webhooks({
  client: paymesh,
  async onPaymentSucceeded(event) {
    console.log("payment.succeeded", event.data.id);
  },
});
src/server/webhooks.ts
import express from "express";
import { Webhooks } from "@paymesh/express";
import { paymesh } from "./paymesh";

const app = express();

app.post(
  "/webhooks/paymesh",
  express.raw({ type: "*/*" }),
  Webhooks({
    client: paymesh,
    async onPaymentSucceeded(event) {
      console.log("payment.succeeded", event.data.id);
    },
  }),
);
src/server/webhooks.ts
import { Hono } from "hono";
import { Webhooks } from "@paymesh/hono";
import { paymesh } from "./paymesh";

const app = new Hono();

app.post(
  "/webhooks/paymesh",
  Webhooks({
    client: paymesh,
  }),
);
src/server/webhooks.ts
import { Elysia } from "elysia";
import { Webhooks } from "@paymesh/elysia";
import { paymesh } from "./paymesh";

const app = new Elysia();

app.post(
  "/webhooks/paymesh",
  Webhooks({
    client: paymesh,
  }),
);

Add plugins only after the core path is live

Once payments, customers, and webhooks are stable, add plugins for operational workflows.

npm install @paymesh/dash @paymesh/audit-logs
src/lib/paymesh.ts
import { auditLog } from "@paymesh/audit-logs";
import { dash } from "@paymesh/dash";

export const paymesh = createClient({
  provider: stripe({
    secret: "sk_test_123",
    webhookSecret: "whsec_123",
  }),
  database: postgres("postgres://postgres:postgres@localhost:5432/paymesh"),
  plugins: [
    dash({
      auth({ request }) {
        const email = request.headers.get("x-user-email");

        if (!email) {
          throw new Error("Unauthorized");
        }

        return {
          id: "user_123",
          type: "user",
          email,
        };
      },
    }),
    auditLog({
      events: ["payment.*", "customer.*", "checkout.*"],
      retention: "1y",
    }),
  ],
});

What "installed correctly" means

An installation is complete when:

  • your app has one shared Paymesh client
  • one provider is configured
  • webhook routes are mounted
  • successful events reach normalized hooks
  • local persistence is configured if you need durable state

If you only installed packages, you are not done.