Developer guide
VEXPay siteAPI reference
Browse documentation

Recipe: sell monthly memberships

A gym, school or SaaS with monthly plans: sign customers up, collect every month by link or automatic debit, and keep access in sync with payment — step by step.

What you will build

A membership business: “Básico” at $15 and “Pro” at $25 a month. Members sign up on your site, pay every month in bolívares, and lose access only after VEX Pay has tried to collect for 3 days. Members who prefer it pay by automatic debit.

StepYou doDone when
1Create your plansYou have two price_… ids
2Listen to billing webhooksYour endpoint verifies customer.subscription.created
3Sign up a memberThey receive the first invoice and pay it
4Keep access in syncAccess follows invoice.paid and customer.subscription.deleted
5Offer automatic debitA test member is debited without paying by hand
6Go liveYour first real member renews

Step 01

Create your plans

curlbash
curl --request POST "${API_URL}/v1/products" \
  --header "content-type: application/json" --header "x-api-key: ${VEXPAY_API_KEY}" \
  --data '{ "name": "Membresía", "priceUsd": "15.00" }'

curl --request POST "${API_URL}/v1/prices" \
  --header "content-type: application/json" --header "x-api-key: ${VEXPAY_API_KEY}" \
  --data '{ "product": "PRODUCT_ID", "unitAmountUsd": 15, "recurring": { "interval": "month" }, "nickname": "Básico" }'

curl --request POST "${API_URL}/v1/prices" \
  --header "content-type: application/json" --header "x-api-key: ${VEXPAY_API_KEY}" \
  --data '{ "product": "PRODUCT_ID", "unitAmountUsd": 25, "recurring": { "interval": "month" }, "nickname": "Pro" }'

Checkpoint: store both price_… ids in your configuration. Plans are created once, not per member.

Step 02

Listen to billing webhooks

Register (or update) your webhook endpoint with the billing events, and verify signatures as in Webhooks. Store the sub_… id on your member record.

curlbash
curl --request POST "${API_URL}/v1/webhooks" \
  --header "content-type: application/json" --header "x-api-key: ${VEXPAY_API_KEY}" \
  --data '{
    "url": "https://example.com/webhooks/vexpay",
    "events": ["customer.subscription.created", "customer.subscription.updated", "customer.subscription.deleted",
               "customer.subscription.reactivated", "invoice.paid", "invoice.payment_failed"]
  }'

Step 03

Sign up a member

When someone picks a plan, create the customer and the subscription from your server. Put your own user id in metadata so every webhook can be matched to the member.

curlbash
curl --request POST "${API_URL}/v1/customers" \
  --header "content-type: application/json" --header "x-api-key: ${VEXPAY_API_KEY}" \
  --data '{ "name": "Ana Pérez", "email": "ana@example.com", "phone": "04141234567", "idNumber": "V12345678", "metadata": { "userId": "u_981" } }'

curl --request POST "${API_URL}/v1/subscriptions" \
  --header "content-type: application/json" --header "x-api-key: ${VEXPAY_API_KEY}" \
  --data '{ "customer": "CUSTOMER_ID", "price": "PRICE_PRO", "metadata": { "userId": "u_981" } }'

# Send the member straight to the first invoice:
curl "${API_URL}/v1/invoices?subscription=SUBSCRIPTION_ID" --header "x-api-key: ${VEXPAY_API_KEY}"
# → items[0].hostedInvoiceUrl

Checkpoint: redirect the member to hostedInvoiceUrl (they also get it by email; in test mode only if their email is your business-profile email), pay it with a test method, and see invoice.paid arrive. Offering a trial instead? Add "trialPeriodDays": 7 and no invoice is issued until it ends.

Step 04

Keep access in sync with payment

EventYour app
invoice.paidAccess on (or extended) until the subscription’s currentPeriodEnd
customer.subscription.updated with status: past_dueKeep access, show a “payment pending” banner with the invoice link
customer.subscription.deletedAccess off. If cancellationReason is payment_failed, tell them paying the invoice brings it back
customer.subscription.reactivatedAccess on again

For a “Cancel membership” button, call POST /v1/subscriptions/:id with { "cancelAtPeriodEnd": true }: the member keeps what they paid for and is not billed again.

Step 05

Offer automatic debit

For members who never want to pay by hand, create a setup session, send them its link, and switch the subscription once they authorize (payment_method.attached). Full details: Automatic charging.

curlbash
curl --request POST "${API_URL}/v1/setup-sessions" \
  --header "content-type: application/json" --header "x-api-key: ${VEXPAY_API_KEY}" \
  --data '{ "customer": "CUSTOMER_ID", "successUrl": "https://example.com/cuenta" }'
# → send "url" to the member

curl --request POST "${API_URL}/v1/subscriptions/SUBSCRIPTION_ID" \
  --header "content-type: application/json" --header "x-api-key: ${VEXPAY_API_KEY}" \
  --data '{ "collectionMethod": "charge_automatically" }'

Checkpoint (test mode): authorize with a phone ending in 0000 — the next invoice is debited and paid on its own. Try 0002 to see an insufficient_funds failure and the retry the next day. With a daily test price you do not wait a month for the next invoice.

Step 06

Go live

  • Complete your business profile (name, logo, email): it is what your members see on invoices and emails.
  • Recreate your plans with the Live key — test and live objects are separate — and switch your server to it.
  • Choose your default collection attempts in Portal → Billing → Settings.
  • If you offer automatic debit, ask VEX Pay to enable it on your live account; until then live setup sessions and charge_automatically answer automatic_charging_unavailable, and send_invoice works as usual.