# Events and Payloads (/docs/developer-resources/guides/webhook/structure-and-events)

When Monime sends a webhook delivery, the request body contains a JSON payload describing the event. This page documents the payload structure, explains the naming conventions, and provides sample payloads for each event type.

## Understanding the payload envelope

Every webhook payload follows the same envelope structure. The envelope contains metadata about the event and API version, while the `data` field holds the resource-specific content.

```json
{
  "apiVersion": "caph.2025-08-23",
  "event": {
    "id": "wke-7b3f2f1b5c1e4a",
    "name": "payment_code.processed",
    "timestamp": "1760201200"
  },
  "object": {
    "id": "pmc-9xY7r1",
    "type": "payment_code"
  },
  "data": {
    "id": "pmc-9xY7r1"
  }
}
```

This consistent structure makes it easy to write generic handling code. Your handler can extract event metadata the same way regardless of event type, then dispatch to specialized logic based on `event.name`.

### Envelope fields explained

The **apiVersion** field indicates which API release generated this payload. The release determines the available event types and the structure of the `data` field. When you create a webhook, you select an API release, and all deliveries use that release's schema.

The **event** object contains metadata about the event itself:

- **id** is a unique identifier for this specific event occurrence. Use this ID for idempotency—if you receive the same event ID twice, you can skip reprocessing. Event IDs are prefixed with `wke-` followed by a random string.
- **name** identifies what happened. Event names follow the pattern `resource.action`, such as `payment.processing_completed` or `checkout_session.expired`. The name tells you which handler to invoke.
- **timestamp** is a Unix timestamp (seconds since epoch) indicating when the event occurred. This is the time the event happened in Monime's system, not the time the delivery was sent.

The **object** field identifies the resource that triggered the event:

- **id** is the resource's unique identifier. You can use this ID to fetch the full resource from the Monime API if you need more data than the payload provides.
- **type** indicates the resource type, such as `payment_code`, `payment`, `checkout_session`, or `payout`.

The **data** field contains a representation of the resource at the time of the event. The structure varies by resource type and often by event type within a resource. For example, a `payment_code.processed` event includes payment details, while a `payment_code.cancelled` event may include only the ID and status.

## Event naming conventions

Event names use the format `resource.action`, where the resource is the type of object involved and the action describes what happened to it.

Resources correspond to Monime objects such as `payment_code`, `payment`,
`checkout_session`, `payout`, `financial_account`, `financial_transaction`,
`internal_transfer`, and `ussd_otp`. Actions describe lifecycle changes such as
`created`, `processing_started`, `processing_completed`, `credited`, `debited`,
`completed`, `failed`, `expired`, `verified`, and `cancelled`.

When your handler receives an event, parse the name to determine routing:

```javascript
function handleWebhook(payload) {
  const [resource, action] = payload.event.name.split('.');

  switch (resource) {
    case 'payment':
      return handlePaymentEvent(action, payload);
    case 'payout':
      return handlePayoutEvent(action, payload);
    // ...
  }
}
```

## Current release event registry

The webhook release determines which event names you can select. Caph is the
latest release. SiriusB uses the `2024-08-01` release identifier.

**Tab: Caph (Latest)**

- `checkout_session.cancelled`
- `checkout_session.completed`
- `checkout_session.expired`
- `financial_account.created`
- `financial_account.credited`
- `financial_account.debited`
- `financial_transaction.created`
- `internal_transfer.completed`
- `internal_transfer.created`
- `internal_transfer.failed`
- `payment.created`
- `payment.processing_completed`
- `payment.processing_started`
- `payment_code.cancelled`
- `payment_code.completed`
- `payment_code.expired`
- `payment_code.processed`
- `payout.completed`
- `payout.failed`
- `ussd_otp.expired`
- `ussd_otp.verified`

**Tab: SiriusB (2024-08-01)**

- `payment_code.completed`
- `payment_code.created`
- `payment_code.expired`
- `payment_code.processed`
- `payout.completed`
- `payout.created`
- `payout.delayed`
- `payout.failed`
- `payout.processed`
- `payout.reversed`

The payload examples below retain the authoritative envelope and `data` shapes
from the imported webhook documentation branch. Each example is labelled with
an event and release from the current registry.

## Payment code events

Payment codes are merchant-generated codes that customers use to initiate payments, often through USSD. Events track the payment code lifecycle from creation through completion, expiration, or cancellation.

Caph includes `payment_code.processed`, `payment_code.completed`,
`payment_code.expired`, and `payment_code.cancelled`. SiriusB includes
`payment_code.created`, `payment_code.processed`, `payment_code.completed`, and
`payment_code.expired`.

For full field definitions, see the [Payment Code object reference](/apis/versions/caph-2025-08-23/payment-code/object).

**Tab: payment\_code.created (SiriusB)**

Fired when a new payment code is created. The payload contains the complete payment code configuration including amount, expiration time, authorized providers, and metadata.

```json
{
  "apiVersion": "2024-08-01",
  "event": {
    "id": "wke-21a1c2f0b7e146",
    "name": "payment_code.created",
    "timestamp": "1760201200"
  },
  "object": {
    "id": "pmc-9xY7r1",
    "type": "payment_code"
  },
  "data": {
    "id": "pmc-9xY7r1",
    "mode": "one_time",
    "status": "pending",
    "name": "Home EDSA Meter Top-up",
    "amount": {
      "currency": "SLE",
      "value": 10000
    },
    "enable": true,
    "expireTime": "2026-02-05T13:45:00Z",
    "ussdCode": "*715*1*7344331308#",
    "authorizedProviders": ["m17", "m18"],
    "financialAccountId": "fac-4nS8kF",
    "createTime": "2026-02-05T13:30:00Z",
    "metadata": {
      "order_id": "ord_123"
    }
  }
}
```

**Tab: payment\_code.processed**

Fired when a payment is received against the code but not yet completed. The `processedPaymentData` field contains details about the incoming payment, including the payment ID and channel information.

```json
{
  "apiVersion": "caph.2025-08-23",
  "event": {
    "id": "wke-4f8c3c2a9a401a",
    "name": "payment_code.processed",
    "timestamp": "1760201502"
  },
  "object": {
    "id": "pmc-9xY7r1",
    "type": "payment_code"
  },
  "data": {
    "id": "pmc-9xY7r1",
    "mode": "one_time",
    "status": "processing",
    "amount": {
      "currency": "SLE",
      "value": 10000
    },
    "processedPaymentData": {
      "amount": {
        "currency": "SLE",
        "value": 10000
      },
      "orderId": "ord-8Hk2f",
      "paymentId": "pay-2b8Xk",
      "orderNumber": "808956084535",
      "channelData": {
        "providerId": "m17",
        "accountId": "23278341807",
        "reference": "MP240830.1142.B79174"
      },
      "financialTransactionReference": "ftx-0u3fA"
    },
    "updateTime": "2026-02-05T13:31:10Z"
  }
}
```

**Tab: payment\_code.completed**

Fired when the payment code reaches its terminal successful state. For one-time codes, this means the expected payment was received and confirmed. The payload contains the final status.

```json
{
  "apiVersion": "caph.2025-08-23",
  "event": {
    "id": "wke-6b04d7e1a1f71c",
    "name": "payment_code.completed",
    "timestamp": "1760201800"
  },
  "object": {
    "id": "pmc-9xY7r1",
    "type": "payment_code"
  },
  "data": {
    "id": "pmc-9xY7r1",
    "mode": "one_time",
    "status": "completed",
    "amount": {
      "currency": "SLE",
      "value": 10000
    },
    "enable": true,
    "updateTime": "2026-02-05T13:36:40Z"
  }
}
```

**Tab: payment\_code.expired**

Fired when a payment code passes its `expireTime` without being used. The status is now `expired`, and the code can no longer accept payments.

```json
{
  "apiVersion": "caph.2025-08-23",
  "event": {
    "id": "wke-58cf2b0a9d1c43",
    "name": "payment_code.expired",
    "timestamp": "1760205200"
  },
  "object": {
    "id": "pmc-9xY7r1",
    "type": "payment_code"
  },
  "data": {
    "id": "pmc-9xY7r1",
    "mode": "one_time",
    "status": "expired",
    "expireTime": "2026-02-05T13:45:00Z",
    "updateTime": "2026-02-05T13:45:00Z"
  }
}
```

**Tab: payment\_code.cancelled**

Fired when a payment code is cancelled via the API or dashboard. Cancelled codes cannot accept payments.

```json
{
  "apiVersion": "caph.2025-08-23",
  "event": {
    "id": "wke-2a6b8ad91cfe4e",
    "name": "payment_code.cancelled",
    "timestamp": "1760202100"
  },
  "object": {
    "id": "pmc-9xY7r1",
    "type": "payment_code"
  },
  "data": {
    "id": "pmc-9xY7r1",
    "mode": "one_time",
    "status": "cancelled",
    "updateTime": "2026-02-05T13:35:00Z"
  }
}
```

## Payment events

Payment events track individual payments as they move through processing. These are distinct from payment codes—a payment code might generate a payment, but the payment has its own lifecycle.

Caph includes `payment.created`, `payment.processing_started`, and
`payment.processing_completed`.

For full field definitions, see the [Payment object reference](/apis/versions/caph-2025-08-23/payment/object).

**Tab: payment.created**

Fired when a new payment is created. The payment starts in `pending` status. The payload includes the amount, channel type, and order number.

```json
{
  "apiVersion": "caph.2025-08-23",
  "event": {
    "id": "wke-0c8f1b2d3e4f5a",
    "name": "payment.created",
    "timestamp": "1760201200"
  },
  "object": {
    "id": "pay-4kL2n",
    "type": "payment"
  },
  "data": {
    "id": "pay-4kL2n",
    "status": "pending",
    "amount": {
      "currency": "SLE",
      "value": 5000
    },
    "channel": {
      "type": "momo"
    },
    "orderNumber": "901234567890",
    "createTime": "2026-02-05T13:30:00Z"
  }
}
```

**Tab: payment.processing\_started**

Fired when the payment enters processing. This indicates the payment channel is actively working on the transaction. The status transitions from `pending` to `processing`.

```json
{
  "apiVersion": "caph.2025-08-23",
  "event": {
    "id": "wke-3a4b5c6d7e8f90",
    "name": "payment.processing_started",
    "timestamp": "1760201250"
  },
  "object": {
    "id": "pay-4kL2n",
    "type": "payment"
  },
  "data": {
    "id": "pay-4kL2n",
    "status": "processing",
    "amount": {
      "currency": "SLE",
      "value": 5000
    },
    "channel": {
      "type": "momo"
    },
    "updateTime": "2026-02-05T13:30:50Z"
  }
}
```

**Tab: payment.processing\_completed**

Fired when the payment completes successfully. Funds have been transferred and the transaction is confirmed. The `financialTransactionReference` links to the underlying financial transaction.

```json
{
  "apiVersion": "caph.2025-08-23",
  "event": {
    "id": "wke-9f8e7d6c5b4a32",
    "name": "payment.processing_completed",
    "timestamp": "1760201400"
  },
  "object": {
    "id": "pay-4kL2n",
    "type": "payment"
  },
  "data": {
    "id": "pay-4kL2n",
    "status": "completed",
    "amount": {
      "currency": "SLE",
      "value": 5000
    },
    "channel": {
      "type": "momo"
    },
    "financialTransactionReference": "ftx-2a9kQ",
    "updateTime": "2026-02-05T13:32:10Z"
  }
}
```

## Checkout session events

Checkout sessions represent customer-facing payment flows, typically rendered through Monime's hosted checkout page. Events track whether the customer completed the checkout, cancelled it, or let it expire.

Available events:

- `checkout_session.completed` — The customer completed the checkout and payment succeeded
- `checkout_session.cancelled` — The customer cancelled the checkout
- `checkout_session.expired` — The checkout session expired before completion

For full field definitions, see the [Checkout Session object reference](/apis/versions/caph-2025-08-23/checkout-session/object).

**Tab: checkout\_session.completed**

Fired when a checkout session completes successfully. The customer has paid, and you should fulfill their order. The `orderNumber` helps you match this to your internal records.

```json
{
  "apiVersion": "caph.2025-08-23",
  "event": {
    "id": "wke-5c4b3a2d1e0f9a",
    "name": "checkout_session.completed",
    "timestamp": "1760201600"
  },
  "object": {
    "id": "chk-6mN2p",
    "type": "checkout_session"
  },
  "data": {
    "id": "chk-6mN2p",
    "status": "completed",
    "orderNumber": "123456789012",
    "successUrl": "https://example.com/success",
    "createTime": "2026-02-05T13:20:00Z",
    "updateTime": "2026-02-05T13:33:10Z"
  }
}
```

**Tab: checkout\_session.cancelled**

Fired when the customer clicks cancel or navigates away. No payment was made. You might use this to release reserved inventory or send an abandoned cart reminder.

```json
{
  "apiVersion": "caph.2025-08-23",
  "event": {
    "id": "wke-1a2b3c4d5e6f70",
    "name": "checkout_session.cancelled",
    "timestamp": "1760201550"
  },
  "object": {
    "id": "chk-6mN2p",
    "type": "checkout_session"
  },
  "data": {
    "id": "chk-6mN2p",
    "status": "cancelled",
    "cancelUrl": "https://example.com/cancel",
    "updateTime": "2026-02-05T13:32:30Z"
  }
}
```

**Tab: checkout\_session.expired**

Fired when a checkout session reaches its expiration time without being completed or cancelled. Sessions have a limited lifetime to prevent stale pricing or inventory issues.

```json
{
  "apiVersion": "caph.2025-08-23",
  "event": {
    "id": "wke-7a8b9c0d1e2f34",
    "name": "checkout_session.expired",
    "timestamp": "1760205200"
  },
  "object": {
    "id": "chk-6mN2p",
    "type": "checkout_session"
  },
  "data": {
    "id": "chk-6mN2p",
    "status": "expired",
    "expireTime": "2026-02-05T13:45:00Z",
    "updateTime": "2026-02-05T13:45:00Z"
  }
}
```

## Payout events

Payout events track disbursements from your Monime financial account to external destinations like mobile money accounts. Events indicate whether the payout is in progress, completed successfully, or failed.

Caph includes `payout.completed` and `payout.failed`. SiriusB includes
`payout.created`, `payout.processed`, `payout.delayed`, `payout.completed`,
`payout.failed`, and `payout.reversed`.

For full field definitions, see the [Payout object reference](/apis/versions/caph-2025-08-23/payout/object).

**Tab: payout.completed**

Fired when the payout is successfully delivered to the recipient. The funds have arrived at the destination account.

```json
{
  "apiVersion": "caph.2025-08-23",
  "event": {
    "id": "wke-9a8b7c6d5e4f32",
    "name": "payout.completed",
    "timestamp": "1760201900"
  },
  "object": {
    "id": "pyt-3mP1x",
    "type": "payout"
  },
  "data": {
    "id": "pyt-3mP1x",
    "status": "completed",
    "amount": {
      "currency": "SLE",
      "value": 20000
    },
    "destination": {
      "type": "momo"
    },
    "updateTime": "2026-02-05T13:38:40Z"
  }
}
```

**Tab: payout.failed**

Fired when a payout cannot be completed. The `failureDetail` field explains what went wrong—insufficient funds, invalid destination, network issues, etc. Depending on the failure reason, you may be able to retry.

```json
{
  "apiVersion": "caph.2025-08-23",
  "event": {
    "id": "wke-2b3c4d5e6f7081",
    "name": "payout.failed",
    "timestamp": "1760202000"
  },
  "object": {
    "id": "pyt-3mP1x",
    "type": "payout"
  },
  "data": {
    "id": "pyt-3mP1x",
    "status": "failed",
    "amount": {
      "currency": "SLE",
      "value": 20000
    },
    "failureDetail": {
      "code": "fund_insufficient",
      "message": "Source account lacks sufficient funds."
    },
    "updateTime": "2026-02-05T13:40:10Z"
  }
}
```

## Other Caph events

Caph also publishes these events:

- `financial_account.created`
- `financial_account.credited`
- `financial_account.debited`
- `financial_transaction.created`
- `internal_transfer.created`
- `internal_transfer.completed`
- `internal_transfer.failed`
- `ussd_otp.expired`
- `ussd_otp.verified`

Use the `object.id` and `object.type` fields to identify the affected resource.
The imported branch does not provide additional payload examples for these
events, so this guide does not invent them.

## Implementing idempotency

The same event can be delivered multiple times due to retries, replays, or network conditions. Use the `event.id` field to detect duplicates.

Before processing an event, check whether you have already processed an event with that ID. If so, return success without re-processing. This prevents duplicate side effects like sending the same notification twice or crediting an account multiple times.

```javascript
async function handleWebhook(payload) {
  const eventId = payload.event.id;

  // Check if already processed
  if (await alreadyProcessed(eventId)) {
    return { status: 200 }; // Return success to stop retries
  }

  // Process the event
  await processEvent(payload);

  // Mark as processed
  await markProcessed(eventId);

  return { status: 200 };
}
```

Store processed event IDs in a database with a time-to-live. The TTL should exceed Monime's retry window (about 24 hours) but can be shorter if you also handle duplicates at the business logic layer.

## Handling out-of-order delivery

Events are sent in the order they occur, but network conditions and retries can cause deliveries to arrive out of order. A `payment.processing_completed` event might arrive before `payment.processing_started` if the earlier delivery failed initially.

Your handlers should tolerate this. Options include:

- **Use the timestamp.** The `event.timestamp` field indicates when the event occurred. If you receive an event older than what you have already processed for that resource, you can ignore it.
- **Fetch current state.** Use the event as a trigger to fetch the resource's current state from the API, rather than relying solely on the payload.
- **Design for convergence.** Structure your data model so that applying events in any order eventually produces the correct state.

## Partial payloads

Event payloads include a snapshot of the resource at the time of the event, but not necessarily every field. Fields that have not changed or are not relevant to the event may be omitted.

If you need the complete resource, use the `object.id` to fetch it from the Monime API:

```javascript
const resourceId = payload.object.id;
const resourceType = payload.object.type;

// Fetch full resource from API
const fullResource = await monime.get(`/${resourceType}s/${resourceId}`);
```

## Content-Type

All webhook deliveries are sent with `Content-Type: application/json`. The body is UTF-8 encoded JSON. Configure your web framework to handle JSON payloads and preserve raw bytes for signature verification.
