movere Docs

Webhook event catalog

Audience: anyone receiving webhooks from Movere. Platforms on Movere Connect and merchants with their own webhook destination receive slightly different envelopes — both are documented here.

Signature verification, retry schedule, and dedupe guidance live in §6 of the Connect protocol and apply to both audiences unchanged: X-Mooov-Signature: t=<unix>,v1=<hex> where the HMAC-SHA256 input is <t>.<raw body>. Non-2xx responses retry, with up to eight attempts total. Retry delays are 60s, 120s, 240s, 480s, 960s, 1920s, and 3600s, plus retry-loop scheduling time. Transport failures also retry; the last failure dead-letters the delivery. Dedupe on X-Mooov-Delivery or event.id.

1. Two envelopes

Connect platforms

{
  "id": "evt_01HXX…",
  "type": "payment.succeeded",
  "created": "2026-06-10T12:34:56.000Z",
  "merchant": { "id": "merch_lodge_001", "entity_id": "mooov3" },
  "data": { "payment_id": "pay_example", "state": "captured" }
}

created is RFC3339 UTC with exactly three fractional-second digits. The merchant is identified by a nested merchant object — one endpoint receives enabled events for connected tenants with active grants. Ordinary payment fanout currently covers Live only. Test payment events do not reach platform destinations. This Connect envelope has no environment or livemode field; grant-revocation events use a separate direct delivery path after revocation.

Merchant destinations

{
  "id": "evt_01HXX…",
  "type": "payment.captured",
  "occurred_at": "2026-06-10T12:34:56Z",
  "merchant_id": "merch_lodge_001",
  "livemode": true,
  "data": { "payment_id": "pay_example", "state": "captured" }
}

livemode is true for Live merchant events and false for Test. Merchant destinations are selected within the event's environment.

The envelopes differ: merchant webhooks use the internal event names (payment.captured) and a flat merchant_id + occurred_at, while Connect webhooks use the mapped public names (payment.succeeded) and merchant + created. The data payload for an event is the same in both lanes.

2. Event name mapping

Merchant destinations (internal) Connect platforms (public)
payment.captured payment.succeeded
payment.failed payment.failed
payment.refunded payment.refunded
payment.partially_refunded payment.partially_refunded
payment.disputed payment.disputed
payment.authorized — (not delivered to Connect)
payment.voided — (not delivered to Connect)
subscription.activated subscription.activated
subscription.updated subscription.updated
subscription.canceled subscription.canceled
subscription.invoice_paid subscription.invoice_paid
subscription.invoice_failed subscription.invoice_failed
— grant.revoked (Connect-only; fired on integrator- and operator-initiated grant revokes)

3. Payment events

All payment events share a data payload shaped like this. Fields are omitted when empty, never emitted as null:

{
  "data": {
    "payment_id":        "pay_…",
    "state":             "captured",
    "amount":            4200,
    "currency":          "GBP",
    "metadata":          { "your_key": "your_value" },
    "customer_ref":      "mbr_…",
    "payment_method_id": "pm_…"
  }
}
Event (public name) Fires when Extra data fields
payment.succeeded A payment is captured — synchronous server-flow confirm, async hosted-checkout completion, or a subscription cycle's invoice payment —
payment.failed A charge attempt fails terminally failure_reason, failure_code, failure_category
payment.refunded The full captured amount has been refunded Cumulative refund snapshot below
payment.partially_refunded A partial refund leaves a remaining balance Cumulative refund snapshot below
payment.disputed The cardholder opened a dispute/chargeback —

failure_code is the typed code your integration should branch on — e.g. insufficient_funds, card_declined, account_invalid (the merchant's PSP connection broke; see §5.2 of the Connect protocol for the repair flow).

Merchant-destination extras: payment.voided includes void_reason in data; payment.authorized fires when a delayed-capture payment authorizes. Neither is fanned out to Connect platforms.

Refund snapshots

Both refund event types carry these fields in data. All amounts are integer minor units in the payment's currency (1000 = GBP 10.00).

Field Meaning
payment_id Original Mooov payment ID; correlate within the envelope's merchant
state partially_refunded or refunded at the time of this snapshot
refunded_amount Cumulative refunded amount for this payment, not this operation's amount
amount_refunded Compatibility alias with the same value as refunded_amount
captured_amount Captured amount against which the refunds apply
remaining_amount Captured amount minus cumulative refunded amount, never below zero
currency Payment currency

For example, after two GBP 10.00 partial refunds against a GBP 50.00 payment, the second notification contains:

{
  "id": "evt_example_second_partial",
  "type": "payment.partially_refunded",
  "created": "2026-10-07T12:00:00.000Z",
  "merchant": { "id": "merch_example", "entity_id": "mooov3" },
  "data": {
    "payment_id": "pay_example",
    "state": "partially_refunded",
    "currency": "GBP",
    "refunded_amount": 2000,
    "amount_refunded": 2000,
    "captured_amount": 5000,
    "remaining_amount": 3000
  }
}

Refunding the remaining GBP 30.00 produces payment.refunded with state: "refunded", both cumulative fields set to 5000, and remaining_amount: 0. The example omits unrelated optional fields.

The legacy data.amount field is source-dependent: API/portal-origin events use the current refund operation amount, while Stripe-origin events use the captured amount. Do not use amount to calculate refund totals. The refund API response also differs from a webhook: its refunded_amount is the current operation amount, while its refunded_total is cumulative. See the API reference.

Successive partial refunds have distinct event IDs; redelivery of the same event retains its ID. An API response and an API-origin or provider-origin event can all describe the same refunded funds, and different source events can have different IDs. Dedupe delivery by event.id, then merge cumulative totals rather than adding them.

For each merchant/payment, atomically retain the greater of the stored and incoming cumulative totals. Derive the refund status from that total and the captured amount. A late partial snapshot or delayed payment-success event must not reduce the total or undo a full refund. Apply the same merge to API responses using refunded_total; an API response can arrive after a newer webhook. Update the ledger record used by your UI as well as any internal payment-attempt record.

Older stored events are replayed as originally produced and may lack the new snapshot fields. For legacy Stripe-origin events, amount_refunded supplies the cumulative total; do not infer a full refund solely from a legacy event's type. If neither cumulative field is available, reconcile before changing totals. Subscribe your Live Connect destination to both refund event types; Test payment events are not fanned out to platform destinations.

4. Subscription events

Fired when a connected account runs Stripe Subscriptions. The five types, the dual-emit contract (each paid cycle also produces a payment.succeeded with its own refundable payment_id), and the full subscription.invoice_paid payload example are documented in §6 of the Connect protocol.

Event Fires when
subscription.activated First customer.subscription.created on the account
subscription.updated Subscription changed (excluding transitions to canceled)
subscription.canceled Subscription deleted or its status became canceled or incomplete_expired
subscription.invoice_paid A billing cycle's invoice was paid (dual-emitted with payment.succeeded)
subscription.invoice_failed A billing cycle's invoice failed (dual-emitted with payment.failed)

5. Handler checklist

Test all of this against the sandbox before going live.