Herald

Stripe Integration

A hands-on Stripe integration guide for Realm Admins. Follow it end to end and your users will be able to complete subscription payments and credit-pack purchases through Stripe.

A hands-on Stripe integration guide for Realm Admins. Follow it end to end and your users will be able to complete subscription payments and credit-pack purchases through Stripe.

Who this is for

Realm Admins who manage Herald's billing configuration. The whole flow happens in the admin console — no code required.

Prerequisites

  • The Herald admin console is accessible and you have an admin account
  • You have created at least one Realm and one Client App
  • You have a Stripe account. If not, register at stripe.com

Core concepts

Herald does not maintain a local product catalog. You create Products and Prices in the Stripe Dashboard, then configure an Entitlement Mapping in Herald telling the system "Stripe's prod_xxxx corresponds to Herald's pro-plan." After the user pays, Stripe notifies Herald through a Webhook; Herald uses the herald_entitlement_key in the metadata to find the mapping, creates a subscription projection, and grants credits.

Data flow: Stripe Product → Herald Entitlement Mapping → user Checkout → Stripe Webhook (carrying herald_* metadata) → Herald subscription projection + credit grant


Step 1: Configure the Stripe connection

Fill in the Stripe credentials in the Herald admin console.

  1. Find Payment Providers in the left menu and click in
  2. Find Stripe, click Configure
  3. Fill the form:
    • Publishable Key (required): starts with pk_. Get it from Stripe Dashboard → Developers → API Keys. Use a pk_test_ key for the test environment
    • Secret Key (required): starts with sk_. Use an sk_test_ key for the test environment
    • Webhook Secret (required): starts with whsec_. Obtained in the next step
  4. Click Save

Saving valid credentials enables Stripe — there is no separate enable toggle. Herald detects the environment from the API key prefix: sk_test_ goes to test, sk_live_ goes to production.

Step 2: Configure the Stripe Webhook

Stripe pushes payment results to Herald through Webhooks. This is the most critical step of the whole integration.

Create the Webhook endpoint

  1. Open Stripe Dashboard → Developers → Webhooks
  2. Confirm you are in test mode (top-right toggle); configure the test environment first during development
  3. Click Add endpoint
  4. Fill in:
    • Endpoint URL: https://your-herald-domain/api/third/pay/{realmId}/stripe/webhooks
      • Replace {realmId} with your realm ID, e.g. admin
      • For local development, expose Herald with a tool like ngrok
    • Listen to: choose Events (not Account events)

Select Webhook events

Click Select events and check the events below. Missing any one of them breaks the corresponding payment flow.

Checkout events

EventStripe pathHerald handling
checkout.session.completedCheckout → checkout.session.completedComplete checkout; create a subscription or finish a one-time payment
checkout.session.expiredCheckout → checkout.session.expiredCheckout session expired; mark the payment attempt failed
checkout.session.async_payment_succeededCheckout → checkout.session.async_payment_succeededAsync payment (e.g. bank transfer) succeeded; complete the payment attempt
checkout.session.async_payment_failedCheckout → checkout.session.async_payment_failedAsync payment failed; mark the payment attempt failed

Subscription events

EventStripe pathHerald handling
customer.subscription.createdCustomers → customer.subscription.createdNew subscription created; sync the subscription projection and grant credits
customer.subscription.updatedCustomers → customer.subscription.updatedHandle upgrade/downgrade
customer.subscription.pausedCustomers → customer.subscription.pausedSubscription paused; sync state
customer.subscription.resumedCustomers → customer.subscription.resumedSubscription resumed; sync state
customer.subscription.deletedCustomers → customer.subscription.deletedSubscription canceled; claw back credits

Payment and refund events

EventStripe pathHerald handling
payment_intent.succeededPayments → payment_intent.succeededOne-time payment (credit-pack purchase) succeeded
payment_intent.payment_failedPayments → payment_intent.payment_failedPayment failed; mark the payment attempt
charge.refundedPayments → charge.refundedRefund; claw back credits proportionally
charge.dispute.createdPayments → charge.dispute.createdDispute opened; mark the subscription as disputed
charge.dispute.closedPayments → charge.dispute.closedDispute closed: lost cancels the subscription, won restores it

Billing and invoice events

EventStripe pathHerald handling
invoice.payment_succeededBilling → invoice.payment_succeededRenewal succeeded; grant renewal credits and sync subscription state
invoice.payment_failedBilling → invoice.payment_failedInvoice payment failed; mark the payment attempt
invoice.payment_action_requiredBilling → invoice.payment_action_requiredCustomer action required (e.g. 3D Secure); log
invoice.createdBilling → invoice.createdSync the Stripe invoice into Herald's external invoices
invoice.finalizedBilling → invoice.finalizedSync the Stripe invoice into Herald's external invoices
invoice.paidBilling → invoice.paidSync the Stripe invoice into Herald's external invoices
invoice.voidedBilling → invoice.voidedSync the Stripe invoice into Herald's external invoices

All 20 events above must be checked. Herald logs and returns 200 for events outside this list — they are safely ignored.

Get the Webhook Secret

  1. After the endpoint is created, find Signing secret on the endpoint detail page
  2. Click Reveal, copy the value starting with whsec_
  3. Go back to the Herald admin console → Payment Providers → Stripe config, and paste it into Webhook Secret

Production

Before going live, switch the Stripe Dashboard to production mode and repeat these steps to create the production Webhook endpoint. The production API Key and Webhook Secret are completely independent of the test environment.

Step 3: Create products in Stripe

Create your products in Stripe Dashboard → Products.

Subscription products

  1. Click Add product
  2. Fill in a product name (e.g. "AI Writing Assistant Pro")
  3. Set the price to Recurring, choose monthly or yearly
  4. After creation, note the Product ID (like prod_xxxxxxxxxxxx) and Price ID (like price_xxxxxxxxxxxx)

Herald uses Stripe's inline pricing to create the Checkout Session, so you don't need to pre-create a Price in Stripe. But the Product ID is a required field for the mapping.

Credit-pack products (one-time purchase)

  1. Create in the same Products page
  2. Set the price to One time
  3. Note the Product ID

Step 4: Sync Stripe products into Herald

Sync Stripe products into Entitlement Mappings in Herald.

  1. In the Herald admin console left menu, click Entitlement Mappings
  2. Click Sync Provider Products
  3. Select provider Stripe
  4. Wait for sync to finish

After sync you will see the list of products pulled from Stripe. Each record contains:

  • External Product ID (e.g. prod_xxxx)
  • External Price ID (e.g. price_xxxx)
  • An auto-generated Entitlement Key (format stripe-{normalized_product_id})
  • The owning Credit Account (newly synced mappings default to the Realm's registration receiving pool)
  • Points policy state

The list's primary label is the Stripe product name. When the product name is missing, it falls back to the External Product ID, then a placeholder — never an empty label. The product filter matches on product name; hitting the external ID also counts.

Price display respects Stripe's unit conversion. Stripe syncs the price as the smallest currency unit integer (e.g. 999 means 9.99); the list and detail convert it to the major unit (9.99). This conversion branch applies only to Stripe — Creem never hits it.

Sync also pulls Stripe Product.metadata and Price.metadata (the merchant-defined key/value pairs you set in the Stripe Dashboard) into the mapping detail's display info, read-only. Re-sync uses Stripe's current value as the override. Herald never writes back to Stripe metadata; the authoritative source is the Stripe Dashboard.

The billing period (subscription period) comes from Stripe's Price.recurring.interval (day/week/month/year); the frontend is read-only and does not accept manual input. Even if you submit a period value on save/update, the backend will not use it to overwrite the synced value.

The auto-generated Entitlement Key is usually not what you want. The next step adjusts it.

Step 5: Configure the Entitlement Mapping

Each mapping tells Herald: "this Stripe product = this Herald entitlement".

  1. In the Entitlement Mappings list, find the mapping to configure
  2. Click edit and modify these fields:
    • Entitlement Key (required): change it to something you recognize, e.g. pro-monthly. This key is used in SDK queries, points policy, and webhook handling — try not to change it after creation
    • Billing Type: Recurring (subscription) or OneTime (one-time purchase)
    • Points Per Period: how many credits to grant per billing period
    • Grant On Subscribe: whether to grant credits on first subscription
    • Validity Days: credit validity in days; 0 or empty means never expires
    • Enabled: whether it is enabled. When disabled, webhooks still update the subscription projection, but no credits are granted
  3. Click save

Anchor to a Credit Account

Each mapping must be attached to a Credit Account. Synced mappings default to this Realm's registration receiving-pool account. If this plan belongs to a separate business line, go to the Credit Accounts page and move it to the right account.

The Credit Account decides two things: which pool the user's credits land in after purchase, and which Client Apps can spend those credits. See Billing Architecture for the Credit Account concept.

Stripe Metadata import (optional)

Stripe Products and Prices support metadata. If you add herald_entitlement_key to a Product or Price in the Stripe Dashboard, Herald reads it automatically on sync. But this is only an import source, not the source of truth — the local Herald configuration wins.

Step 6: User payment flow

After configuration, the user-side flow is as follows.

Subscription payment (Checkout Session)

  1. The user picks a plan in your app
  2. Your app calls Herald's Checkout API (passing entitlement_key, payment_provider=stripe)
  3. Herald looks up the Entitlement Mapping to find the Stripe product ID
  4. Herald calls the Stripe API to create a Checkout Session, writing herald_entitlement_key, herald_user_id, herald_client_app_id, herald_realm_id, herald_billing_kind into metadata
  5. Returns a Stripe payment-page URL
  6. The user completes payment on the Stripe page
  7. Stripe fires checkout.session.completed and customer.subscription.created Webhooks
  8. Herald parses the entitlement_key from metadata, creates the subscription projection, and grants credits
  9. On each subsequent billing period, Stripe charges automatically and fires invoice.payment_succeeded; Herald grants renewal credits

One-time payment (credit pack / Payment Intent)

  1. The user picks a credit pack
  2. Your app calls the Herald Payment Attempt API
  3. Herald creates a PaymentAttempt and calls the Stripe Payment Intent API
  4. Returns client_secret
  5. The frontend uses Stripe.js to render the payment form
  6. The user completes payment; Stripe fires payment_intent.succeeded
  7. Herald completes the payment and grants topup_credit

Refund

When a user initiates a refund in the Stripe Dashboard:

  • Stripe fires charge.refunded
  • Herald claws back credits based on refund type (top-up refunds claw back proportionally; subscription refunds claw back unused credits)

Step 7: Verify the result

Check Entitlement Mapping status

  1. On the Entitlement Mappings page, confirm the mapping's Enabled status and points policy

Check subscription status

  1. Click Subscriptions in the left menu to see the subscription projection list
  2. Confirm the entitlement_key, status, and payment_provider fields are correct

Check change history

  1. Click Subscription History to see the full change timeline for a subscription
  2. Each change record carries event type, entitlement change, and timestamp

If webhooks are firing normally, the state updates within a few seconds. If it is stuck, see the troubleshooting below.


Troubleshooting

Webhook not received

  1. Confirm the Herald deployment's public address is reachable from Stripe's servers (use ngrok for local dev)
  2. Confirm the realm ID in the Webhook endpoint URL is correct
  3. In Stripe Dashboard → Webhooks → your endpoint, check the Attempts log
  4. Confirm the Webhook Secret from Step 1 matches the endpoint's Signing secret

Webhook signature verification failed

  • The Webhook Secret is wrong (must start with whsec_)
  • A reverse proxy in front of Herald modified the request body (can happen when Nginx's proxy_request_buffering is off)
  • The signature timestamp is more than 15 minutes old (clock skew)

Payment succeeded but the subscription is not active

Most likely the Webhook did not reach Herald. Check the Herald logs for a POST /api/third/pay/{realmId}/stripe/webhooks request.

Common causes:

  • The Webhook URL in the Stripe Dashboard is wrong
  • A server firewall is blocking Stripe's callback
  • An SSL certificate problem

Stripe products not visible in Entitlement Mapping

Confirm three things:

  1. The Stripe API Key is configured correctly (Payment Providers page)
  2. You have run Sync Provider Products
  3. The Products actually exist in the Stripe Dashboard

Test card numbers

The Stripe test environment uses these cards:

  • Success: 4242 4242 4242 4242
  • Requires authentication: 4000 0025 0000 3155
  • Declined: 4000 0000 0000 0002

Use any future expiry date and any 3-digit CVC.


Checklist

  • Stripe configured on the Payment Providers page (Publishable Key, Secret Key, Webhook Secret filled in and enabled)
  • Webhook endpoint created in the Stripe Dashboard, all 20 events checked
  • Webhook Secret matches the Stripe endpoint's Signing secret
  • Product created in Stripe, Product ID noted
  • Sync Provider Products run on the Herald Entitlement Mappings page
  • Entitlement Key and points policy reviewed and adjusted
  • Mapping status is Enabled
  • One full payment flow completed with test keys and a test card

On this page