When your chatbot receives the `session_id`, mount the widget into a `` inside the chat message:
```javascript theme={"dark"}
const payment = Maven.createPayment({
sessionId: "",
onSuccess: (result) => {
// result = { transaction_id, card_brand, card_last4, amount_cents, currency, status }
// Typical things to do here (all optional — Maven already shows
// a green "Payment confirmed" screen inside the iframe):
chatbot.sendMessage(`Paid — ${result.card_brand} •• ${result.card_last4}`);
await db.orders.markPaid(orderId, result.transaction_id);
// or: window.location = `/thanks?txn=${result.transaction_id}`;
},
onFailure: (error) => {
// error = { error_code, error_message }
chatbot.sendMessage(`Payment didn't go through: ${error.error_message}`);
},
});
payment.mount("#chat-payment-slot");
```
The iframe appears inline. Customer types their card, hits pay. Your callbacks fire — `onSuccess` and `onFailure` are **hooks into your app** so you can continue the chatbot conversation, update your database, or redirect the customer. The widget itself handles the card form, charging, and the success/failure UI inside the iframe.
Configure a webhook URL on the project. Maven fires the same `payment-success` / `payment-failed` event for widget payments as it does for voice — with an additional `source: "chat"` field.
See [Webhooks](/integrations/webhooks) for the full payload.
## Common errors
`POST /v1/widget-sessions` can return these if something's off in your setup:
| Status | Code | What it means | Fix |
| ------ | ----------------------- | ---------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| `401` | — | Bad or missing API key | Check the `Authorization: Bearer mvn_test_...` header |
| `404` | `project_not_found` | Project slug doesn't exist in your org | Double-check the `project` value matches a slug in your dashboard |
| `422` | `gateway_not_connected` | Gateway isn't connected for this env | Connect the gateway in the dashboard (Gateways tab). Test keys need test credentials; live keys need live credentials. |
| `422` | `invalid_amount` | `amount_cents` is 0 or negative in charge mode | Pass `amount_cents > 0`, or use `mode: "tokenize"` to save a card without charging |
| `429` | — | Rate limited | Back off; defaults are 20 session creates/minute per IP |
If the widget mounts but doesn't render, check your browser console. The usual culprit is `Maven is not defined` — that means the `