VibeAny Docs

Webhooks, Subscriptions, and Credits

Understand durable fulfillment, monthly credit grants, idempotency, and the manual refund workflow.

VibeAny treats signed webhooks and database state as the source of truth for billing. Checkout return pages are user feedback only.

Durable webhook pipeline

All payment providers deliver to the same route:

POST /api/payments/webhook

The active provider verifier processes the delivery in this order:

  1. read the unmodified request body;
  2. verify the active provider's signature;
  3. normalize the provider event into application billing events;
  4. claim a webhook inbox entry by provider plus event ID or payload hash;
  5. apply subscription or credit fulfillment;
  6. mark the inbox entry processed only after fulfillment succeeds.

A duplicate delivery returns success with deduped: true. If fulfillment fails, the inbox claim is released so the provider can retry. Invalid signatures return HTTP 400; incomplete provider configuration returns HTTP 503.

Because webhook verification is also single-provider, a webhook from the inactive provider is not routed to its verifier. This is why changing PAYMENTS_PROVIDER is not a migration strategy.

Subscription state

Subscription webhooks store:

  • the provider and external subscription/customer IDs;
  • the plan and billing interval;
  • lifecycle status and cancellation-at-period-end state;
  • current period boundaries;
  • provider event time and the next scheduled credit grant.

Writes are serialized per user, and older provider events do not overwrite newer state. The database enforces one subscription row per user.

Successful invoice events record the payment lifecycle but do not directly grant credits. Failed invoice handling is currently a no-op; dunning, access restriction, and customer notification are product policy that you must implement before promising them.

Independent monthly subscription credits

Subscription credits are granted on an application-owned monthly schedule rather than on every invoice webhook. This keeps annual subscriptions on monthly credit allotments.

Vercel calls the protected scheduler daily:

GET /api/cron/subscription-credits
Authorization: Bearer <CRON_SECRET>

The included schedule is 5 0 * * *, or 00:05 UTC each day. Configure a strong server-only CRON_SECRET whenever subscription products are enabled.

For each active, trialing, or scheduled-for-cancellation subscription, the scheduler:

  1. uses the subscription period start as the monthly anchor;
  2. creates an idempotent subscription_grant credit lot;
  3. expires that lot at the next monthly grant or the subscription period end, whichever comes first;
  4. advances the next-grant timestamp with a compare-and-set update.

Webhook processing also runs the due-grant use case, so a newly activated subscription can receive a due initial grant without waiting for the next daily cron.

Credit ledger behavior

Credits are consumed from the oldest unexpired lot first. The ledger keeps a cached balance and a FIFO projection, while source identifiers prevent the same payment or scheduled grant from being credited twice.

Built-in credit packs add:

SKUCreditsExpiration
credits_small100365 days after fulfillment
credits_medium500365 days after fulfillment
credits_large1,500365 days after fulfillment

AI image generation costs 10 credits per image. It deducts credits before calling the model provider and adds them back if provider generation fails.

The Free plan's catalog copy is not an automatic recurring-grant job. If your product promises recurring free credits, add and test an explicit scheduler or onboarding grant.

Manual refund policy

The starter separates refund assessment from refund execution.

Set an exclusive subscription usage threshold:

PAYMENTS_SUBSCRIPTION_REFUND_MAX_USAGE_PERCENT=5

With a value of 5, usage below 5% is eligible for a full-refund recommendation; usage at exactly 5% is not. Credit-package purchases are marked non-refundable by the default policy, except where applicable law requires otherwise.

Run the read-only assessment:

npm run payments:refund:assess -- \
  --product subscription \
  --granted-credits 500 \
  --consumed-credits 24

For a package, pass --product credit-package.

The command does not read the database, issue a refund, cancel a subscription, or change credits. Before acting:

  1. identify the original provider, charge, billing period, and exact credit lot;
  2. calculate consumption from that lot, not the user's aggregate balance;
  3. retain the assessment result;
  4. issue an approved full refund in the original provider's dashboard;
  5. cancel separately if required;
  6. manually reconcile the remaining entitlement and record the final provider refund ID.

Provider refund events are not mapped back to credit lots. Automatic reversal, disputes, and chargebacks require manual reconciliation in the current starter.

Verification

npm run preflight
npm run test:unit
npm run test:credit-ledger:integration
npm run test:subscription:integration

The PostgreSQL integration suites require disposable test databases. Full payment acceptance also requires a provider test payment, public HTTPS webhook delivery, duplicate replay, and database read-back from webhook_inbox, subscriptions, and credit_transactions.

On this page