PayByMy.AI
For merchants

Accept AI-driven payments.

Pay By AI is a thin orchestration layer over Stripe Connect. Your card handling, PCI scope, and payouts all live on Stripe. We broker the agent handshake: the buyer's AI pays a token you issue, we charge the user's stored card, and Stripe settles directly into your Connect account. You fulfil the order on a webhook.

Pricing: $29.99/month with a 15-day free trial. Customers pay nothing. Merchants subscribe before Stripe Connect onboarding; billing is handled by Clerk.
STEP 01

Sign up and subscribe

Go to /sign-up?role=merchant. Create your Clerk account, then start the 15-day free trial on /merchant/subscribe. After that, head to /merchant/onboarding, enter your business name, and click Continue. This creates:

  • a Stripe Connect Express account owned by you — your payouts settle here;
  • a Pay By AI merchant record bound to that Stripe account plus your Clerk user ID, in the current mode (test or live — flip the header toggle first if you need the other);
  • a merchant API key you'll use to mint payment tokens.

Once created, every merchant you own appears on your dashboard— rotate the API key, copy the webhook signing key, find plugin setup guides, all in one place.

STEP 02

Save your API key

Immediately after step 1 you'll land on a page showing the key exactly once. Copy it somewhere safe — your password manager, a secrets vault, your plugin's settings store. It looks like:

pbai_mk_test_a1b2c3d4e5f6...   # test mode
pbai_mk_live_z9y8x7w6v5u4...   # live mode
We don't show it again. If you lose it, generate a new one via the dashboard — the old key stops working immediately.

Then click Continue to Stripe and finish the Stripe Connect forms. Your API key keeps working even before Stripe verification is done — but you won't receive payouts until Stripe has approved your account.

STEP 03

Mint a payment token

When your order system is ready for payment, call Pay By AI to mint a token.

curl -X POST https://www.paybymyai.com/api/payment_requests \
  -H "Authorization: Bearer pbai_mk_test_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "amountCents": 1500,
    "description": "Large pepperoni pizza",
    "merchantMetadata": {
      "order_id": "WOO-42",
      "cart": ["item-1", "item-2"]
    },
    "expiresInSeconds": 900
  }'

Response:

{
  "id": "pr_kXf7z8...",
  "merchantId": "cmabc123",
  "mode": "test",
  "amountCents": 1500,
  "currency": "usd",
  "description": "Large pepperoni pizza",
  "status": "pending",
  "merchantMetadata": { "order_id": "WOO-42", "cart": ["item-1", "item-2"] },
  "expiresAt": "2026-04-19T00:15:00.000Z",
  "createdAt": "2026-04-19T00:00:00.000Z"
}

The id is your token. Hand it to whoever will pay — the user's AI, a checkout page, a QR code. Anyone with this string can pay the request but they can't modify it.

merchantMetadata comes back verbatim in the webhook. Stuff your internal order ID in there so you can look up the order when the payment succeeds.
STEP 04

Wait for the payment

The buyer's AI agent calls Pay By AI's MCP with your token + the user's stored payment method. Pay By AI:

  1. verifies the agent's session and consent bounds;
  2. creates a Stripe PaymentIntent with transfer_data.destination = your Stripe account ID;
  3. on success, fires payment_intent.succeeded and dispatches a webhook to your configured URL.
STEP 05

Handle the webhook

Point webhookUrl at your endpoint. On payment_request.paid, we POST JSON to that URL with two headers:

X-PayByAI-Signature: t=<unix_seconds>,v1=<hmac_sha256_hex>
X-PayByAI-Event-Id: evt_<uuid>

The HMAC is HMAC-SHA256("{timestamp}.{raw_body}", secret). Verify it before trusting the payload.

Node.js (Express) — drop-in verify

import crypto from "node:crypto";
import express from "express";

const app = express();

function verify(rawBody, signatureHeader, secret) {
  if (!signatureHeader) return { ok: false, reason: "missing_signature" };
  const parts = Object.fromEntries(
    signatureHeader.split(",").map((p) => {
      const i = p.indexOf("=");
      return i < 0 ? ["", ""] : [p.slice(0, i).trim(), p.slice(i + 1).trim()];
    }),
  );
  const t = parseInt(parts.t, 10);
  if (!Number.isFinite(t) || !parts.v1) return { ok: false, reason: "malformed" };
  if (Math.abs(Math.floor(Date.now() / 1000) - t) > 300)
    return { ok: false, reason: "stale" };
  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${t}.${rawBody}`)
    .digest("hex");
  const a = Buffer.from(expected, "hex");
  const b = Buffer.from(parts.v1, "hex");
  if (a.length !== b.length) return { ok: false, reason: "bad_signature" };
  return { ok: crypto.timingSafeEqual(a, b) };
}

app.post(
  "/webhooks/paybyai",
  express.raw({ type: "application/json" }),
  async (req, res) => {
    const rawBody = req.body.toString("utf8");
    const result = verify(
      rawBody,
      req.headers["x-paybyai-signature"],
      process.env.PAYBYAI_WEBHOOK_SECRET,
    );
    if (!result.ok) return res.status(400).json({ error: result.reason });

    const event = JSON.parse(rawBody);
    if (event.type === "payment_request.paid") {
      const orderId = event.data.merchantMetadata?.order_id;
      await markOrderPaid(orderId, event.data.paymentRequestId);
    }
    res.json({ received: true });
  },
);

PHP (WordPress / WooCommerce)

add_action('rest_api_init', function () {
  register_rest_route('paybyai/v1', '/webhook', [
    'methods'  => 'POST',
    'callback' => 'paybyai_handle_webhook',
    'permission_callback' => '__return_true',
  ]);
});

function paybyai_handle_webhook(WP_REST_Request $req) {
  $raw = $req->get_body();
  $sig = $req->get_header('x-paybyai-signature');
  $secret = get_option('paybyai_webhook_secret');

  if (!$sig) return new WP_Error('missing', 'no signature', ['status' => 400]);
  parse_str(str_replace(',', '&', $sig), $parts);
  $t = intval($parts['t'] ?? 0);
  if (abs(time() - $t) > 300) {
    return new WP_Error('stale', 'stale timestamp', ['status' => 400]);
  }
  $expected = hash_hmac('sha256', $t . '.' . $raw, $secret);
  if (!hash_equals($expected, $parts['v1'] ?? '')) {
    return new WP_Error('bad_sig', 'invalid signature', ['status' => 400]);
  }

  $event = json_decode($raw, true);
  if ($event['type'] === 'payment_request.paid') {
    $order_id = $event['data']['merchantMetadata']['order_id'] ?? null;
    if ($order_id) {
      $order = wc_get_order($order_id);
      $order->payment_complete($event['data']['stripePaymentIntentId']);
    }
  }
  return ['received' => true];
}
Five-minute timestamp tolerance is our default — adjust if your clock is drifting. Store processed event IDs if you want to guard against retries on your side (we'll eventually add a retry loop).

API reference

POST/api/payment_requestsMerchant key

Mint a token. Body: amountCents, description, currency?, merchantMetadata?, expiresInSeconds?. Returns the payment request object with id as the token.

GET/api/payment_requests/{token}

Read-only preview. Anyone with the token can call this — the token IS the credential. Use it to show the amount and merchant name before your user pays.

POST/api/payment_requests/{token}/cancelMerchant key

Cancel a pending token. Merchants can only cancel their own tokens — we verify ownership.

POST/api/merchants/onboarding

Body: name, returnUrl, refreshUrl. Returns a Stripe onboarding link + the API key. Primarily used by the dashboard; plugins can use it to skip the web form.

Plugin integrations (roadmap)

Official plugins coming for WooCommerce, Shopify, Wix, and Squarespace. Each installs a per-store MCP server that exposes products to AI agents + wires checkout straight through Pay By AI — no code on your end.

Test mode

All new merchants start in test mode by default. The header toggle flips modes; a test-mode key cannot touch live data and vice versa. Use Stripe's 4242 4242 4242 4242 card for test payments.

Merchant setup — accept AI-agent payments — Pay By AI