Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.trymaven.com/llms.txt

Use this file to discover all available pages before exploring further.

Webhooks

Maven sends an HTTP POST to your webhook URL when a payment session completes or fails. This is the recommended way to get payment results back into your system.
Voice and chat widget both use the same webhook. One webhook URL per project, one handler in your code — it processes both channels. Use the caller field to distinguish: it’s the customer’s phone number for voice, and null for chat.

When Webhooks Fire

Webhooks are sent when a session reaches a terminal payment status:
StatusWhen
payment-successCharge was authorized and captured immediately (default)
payment-authorizedAuthorize.net auth-only charge succeeded — funds held but not captured. You settle the auth yourself in your gateway dashboard or via the gateway’s API.
payment-failedCharge declined or gateway error
Webhooks are not sent for expired, cancelled, or abandoned sessions — poll the GET session endpoint for those.

Configuring Your Webhook URL

Set a webhook URL per app in the Maven Dashboard:
  1. Navigate to your app
  2. Go to the Settings tab
  3. Enter your webhook URL (must be HTTPS in production)
  4. Save

Payload Format

All webhook payloads share the same top-level fields. The processor object varies by gateway and mode — see Processor Fields by Gateway for the full specs.

Voice vs Chat

The payload is almost identical for voice and chat — only one field differs:
  • caller is the customer’s phone number for voice sessions, and null for chat sessions (no phone involved)
Everything else — status, processor, card_brand, card_last4, error codes — is the same. A single webhook handler works for both.
{
  "session_id": "a1b2c3d4-...",
  "status": "payment-success",
  "project": "my-store",
  "environment": "live",
  "amount": 49.99,
  "currency": "USD",
  "mode": "charge",
  "gateway": "stripe",
  "caller": null,
  "card_brand": "visa",
  "card_last4": "4242",
  "processor": {
    "payment_intent_id": "pi_xxx",
    "charge_id": "ch_xxx",
    "card_brand": "visa",
    "card_last4": "4242"
  }
}

Full examples by gateway

{
  "session_id": "a1b2c3d4-...",
  "status": "payment-success",
  "project": "my-store",
  "environment": "live",
  "amount": 49.99,
  "currency": "USD",
  "mode": "charge",
  "gateway": "stripe",
  "caller": "+14155551234",
  "card_brand": "visa",
  "card_last4": "4242",
  "processor": {
    "payment_intent_id": "pi_xxx",
    "charge_id": "ch_xxx",
    "receipt_url": "https://pay.stripe.com/receipts/...",
    "payment_method_id": "pm_xxx",
    "card_brand": "visa",
    "card_last4": "4242"
  }
}

Field Reference

FieldTypeDescription
session_idstringSession UUID
statusstring"payment-success", "payment-authorized", or "payment-failed"
projectstringApp slug
environmentstring"test" or "live"
amountnumberAmount in dollars (e.g., 49.99)
currencystringCurrency code (e.g., "USD")
modestring"charge" or "tokenize"
gatewaystring"stripe", "authorizenet", "braintree", "shift4", or "fiserv"
callerstring | nullCaller phone number in E.164 format (voice only; null for chat)
card_brandstring | nullCard brand (visa, mastercard, amex, etc.)
card_last4string | nullLast 4 digits of the card
processorobject | nullGateway-specific response fields (see below)
errorobject | nullPresent only when status is "payment-failed"

Error Object (failures only)

{
  "error": {
    "code": "card_declined",
    "message": "Your card was declined."
  }
}

Processor Fields by Gateway

The processor object contains different fields depending on the gateway and mode (charge vs tokenize).
Charge mode:
{
  "payment_intent_id": "pi_xxx",
  "charge_id": "ch_xxx",
  "receipt_url": "https://pay.stripe.com/receipts/...",
  "payment_method_id": "pm_xxx",
  "card_brand": "visa",
  "card_last4": "4242",
  "postal_code": "90210",
  "exp_month": 12,
  "exp_year": 2027
}
FieldDescription
payment_intent_idStripe PaymentIntent ID
charge_idStripe Charge ID
receipt_urlStripe-hosted receipt URL
payment_method_idStripe PaymentMethod ID
card_brandCard brand (visa, mastercard, etc.)
card_last4Last 4 digits of the card
postal_codeBilling ZIP/postal code (if collected)
exp_monthCard expiration month (if collected)
exp_yearCard expiration year (if collected)
Tokenize mode:
{
  "payment_method_id": "pm_xxx",
  "cloned_payment_method_id": "pm_yyy",
  "cloned_customer_id": "cus_xxx",
  "card_brand": "visa",
  "card_last4": "4242",
  "postal_code": "90210",
  "exp_month": 12,
  "exp_year": 2027
}
FieldDescription
payment_method_idOriginal Stripe PaymentMethod ID (on Maven’s platform account)
cloned_payment_method_idPaymentMethod cloned to your Stripe account (absent if same as payment_method_id)
cloned_customer_idCustomer created on your Stripe account
card_brandCard brand
card_last4Last 4 digits
postal_codeBilling ZIP/postal code (if collected)
exp_monthCard expiration month (if collected)
exp_yearCard expiration year (if collected)

Handling Webhooks

Your webhook endpoint should:
  1. Return a 200 status code quickly (within 5 seconds)
  2. Process the payload asynchronously if needed
  3. Be idempotent — use session_id to deduplicate
from fastapi import FastAPI, Request

app = FastAPI()

@app.post("/webhooks/maven")
async def maven_webhook(request: Request):
    payload = await request.json()
    session_id = payload["session_id"]
    status = payload["status"]

    if status == "payment-success":
        card_last4 = payload.get("card_last4")
        caller = payload.get("caller")  # phone for voice, None for chat
        processor = payload.get("processor", {})
        # Update your order, send receipt, etc.
        await handle_payment_success(session_id, processor)
    elif status == "payment-failed":
        error = payload.get("error", {})
        await handle_payment_failure(session_id, error)

    return {"status": "ok"}

Best Practices

Return a 200 response immediately and process the webhook asynchronously. If your endpoint times out, the webhook will not be retried.
Use the session_id to deduplicate — check if you’ve already processed this session before acting on it.
Verify that the session_id in the webhook belongs to your organization by checking it against the API:
GET /v1/sessions/{session_id}
Webhook delivery is fire-and-forget. If your endpoint is down, the webhook is lost. For critical flows, also poll the session status as a fallback.