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.
| Step | You do | Done when |
|---|---|---|
| 1 | Create your plans | You have two price_… ids |
| 2 | Listen to billing webhooks | Your endpoint verifies customer.subscription.created |
| 3 | Sign up a member | They receive the first invoice and pay it |
| 4 | Keep access in sync | Access follows invoice.paid and customer.subscription.deleted |
| 5 | Offer automatic debit | A test member is debited without paying by hand |
| 6 | Go live | Your first real member renews |
Step 01
Create your plans
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.
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.
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].hostedInvoiceUrlCheckpoint: 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
| Event | Your app |
|---|---|
invoice.paid | Access on (or extended) until the subscription’s currentPeriodEnd |
customer.subscription.updated with status: past_due | Keep access, show a “payment pending” banner with the invoice link |
customer.subscription.deleted | Access off. If cancellationReason is payment_failed, tell them paying the invoice brings it back |
customer.subscription.reactivated | Access 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.
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_automaticallyanswerautomatic_charging_unavailable, andsend_invoiceworks as usual.