lomi.

How should I handle webhooks?

Use webhooks to reconcile payment, checkout, refund, subscription, and payout events from your server.

Webhooks tell your backend when something important happened after a customer or provider action. Use them for reconciliation, fulfillment, emails, subscriptions, and ledger updates.

What are webhooks for?

Use webhooks to:

  • Confirm a payment completed before fulfilling an order.
  • Learn when a payment failed, expired, or was cancelled.
  • Track subscription creation, renewal, and cancellation events.
  • Reconcile refunds, payouts, and transaction state changes.
  • Recover from browser redirects that customers never complete.

Minimum safe handler

Your webhook endpoint should:

  1. Read the raw request body.
  2. Verify the signature with your webhook secret.
  3. Store the event ID or delivery ID.
  4. Ignore duplicate events safely.
  5. Fetch related resources from the API when you need authoritative state.
  6. Return a fast 2xx response after the event is accepted.

Common mistakes

MistakeSafer behavior
Trusting only the success redirectUse webhooks or server-side API reads for fulfillment
Parsing JSON before signature verificationVerify against the raw request body
Updating orders without idempotencyStore processed event IDs
Assuming event order is perfectFetch final resource state when needed
Doing slow work inlineQueue long-running jobs after accepting the webhook

What should I test?

Test successful, failed, duplicate, and delayed events in sandbox. Confirm your dashboard state, order state, and customer communication all agree.

Webhook security

To ensure the security and integrity of webhook events, lomi. signs each request sent to your endpoint using a unique secret associated with that webhook.

  1. Signature Header: Each webhook request includes an `X-Lomi-Signature` HTTP header.
  2. Webhook Secret: When you create a webhook endpoint via the API, a `secret` (prefixed with `whsec_`) is returned. Store this secret securely, as it will not be shown again.
  3. Verification: Your endpoint should verify the signature by computing an `HMAC-SHA256` hash of the raw request body using the webhook secret and comparing it to the signature provided in the header.

Outbound delivery: Requests from lomi. to your URL are not authenticated with `Authorization` / `X-API-Key`, only signature verification applies. Putting the signing secret in an `Authorization` header on your expectation side will not help; middleware that requires Bearer or API-key auth on every POST will typically return `401` (details).

Refer to the Webhook Signature Verification guide for detailed implementation examples in various languages.

Webhook events

When creating or updating a webhook, you subscribe it to specific event types. lomi. will only send notifications for the events you've subscribed to.

Event EnumDescriptionData Payload Type
`PAYMENT_CREATED`A new payment attempt has been initiated.`Transaction`
`PAYMENT_SUCCEEDED`A one-time payment is successful.`Transaction`
`PAYMENT_FAILED`A one-time payment attempt failed.`Transaction`
`SUBSCRIPTION_CREATED`A new subscription is created.`Subscription`
`SUBSCRIPTION_UPDATED`A subscription changed (pause, resume, plan change, cancel scheduled, etc.).`Subscription` (with previous_attributes)
`SUBSCRIPTION_RENEWED`A subscription successfully renews.`Subscription`
`SUBSCRIPTION_CANCELLED`A subscription is cancelled or expired.`Subscription`
`REFUND_COMPLETED`A refund is successfully processed.`Refund`
`REFUND_FAILED`A refund attempt failed.`Refund`
`REFUND_CREATED`A refund attempt was created.`Refund`
`DISPUTE_CREATED`A card dispute was opened.`Dispute`
`DISPUTE_UPDATED`Dispute status or evidence changed.`Dispute`
`DISPUTE_CLOSED`A dispute reached a terminal state.`Dispute`
`test.webhook`A test event generated via the API.`TestPayload`

Renewal payment failures emit PAYMENT_FAILED on the failed transaction. There is no separate subscription payment-failed webhook. Use SUBSCRIPTION_RENEWED for successful billing cycles. See Subscriptions and Checkout behavior: Subscription checkout.

SUBSCRIPTION_UPDATED payload

Non-terminal subscription changes use a structured event envelope: current state in object, what changed in previous_attributes, and optional context for debugging.

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "event": "SUBSCRIPTION_UPDATED",
  "timestamp": "2026-06-11T14:30:00.000Z",
  "data": {
    "object": {
      "subscription_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "status": "paused",
      "price_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
      "cancel_at_period_end": false,
      "next_billing_date": "2026-07-01",
      "plan_name": "Pro Monthly",
      "billing_interval": "month"
    },
    "previous_attributes": {
      "status": "active"
    },
    "context": {
      "source": "merchant_api",
      "actor": "merchant"
    }
  }
}

Reconcile entitlements from data.object. Use previous_attributes to branch logic (e.g. plan upgrade vs pause). context.source is merchant_api, customer_portal, or cron.

(Other event payloads are documented under their API resources, e.g. Transactions, Subscriptions.)

Webhook subscriptions and JSON payloads use SCREAMING_SNAKE_CASE event names (for example PAYMENT_SUCCEEDED). Some docs or OpenAPI tooling may show dotted names (for example payment.succeeded) as a human-readable alias for the same logical event, use the enum values above when configuring endpoints and when comparing event in request bodies.

For operations guidance (retries, idempotency, delivery logs), see Webhook reliability.

Testing webhooks

You can test your webhook endpoints using the API or `curl`.

Create a webhook for testing

First, create a webhook endpoint pointing to a test receiver (e.g., using a service like Webhook.site or a local tunnel like ngrok).

# Replace API_KEY and URL
curl -X POST "https://api.lomi.africa/webhooks" \
  -H "Content-Type: application/json" \
  -H "X-API-Key: YOUR_API_KEY" \
  -d '{
    "url": "YOUR_TEST_WEBHOOK_URL",
    "authorized_events": ["PAYMENT_SUCCEEDED", "test.webhook"],
    "description": "Test Endpoint"
  }'

Note the `id` and `secret` from the response.

Send a test event

Use the `POST /webhooks/{id}/test` endpoint to send a predefined `test.webhook` event to your endpoint.

# Replace WEBHOOK_ID and API_KEY
curl -X POST "https://api.lomi.africa/webhooks/YOUR_WEBHOOK_ID/test" \
  -H "X-API-Key: YOUR_API_KEY"

Check your test receiver to confirm the event was received and verify the signature using the stored secret.

Delivery logs and retries

lomi. logs webhook delivery attempts and provides endpoints to view these logs and manually retry failed deliveries.

Get webhook delivery logs

Retrieves delivery logs for a specific webhook, showing success/failure status, timestamps, response codes, etc.

Endpoint: `GET /webhooks/{id}/logs`

Path parameters:

ParameterTypeRequiredDescription
`id``string`YesID of the webhook (`wh_...`)

Query parameters (optional):

ParameterTypeDescription
`limit``integer`Number of logs to return (max 100)
`offset``integer`Number of logs to skip (pagination)
`success``boolean`Filter by successful deliveries
`failed``boolean`Filter by failed deliveries

Example request:

# Replace WEBHOOK_ID and API_KEY
curl -X GET "https://api.lomi.africa/webhooks/YOUR_WEBHOOK_ID/logs?limit=10&failed=true" \
  -H "X-API-Key: YOUR_API_KEY"

Retry webhook delivery

Manually triggers a retry for a specific failed delivery log entry.

Endpoint: `POST /webhooks/{webhook_id}/logs/{log_id}/retry`

Path parameters:

ParameterTypeRequiredDescription
`webhook_id``string`YesID of the webhook (`wh_...`)
`log_id``string`YesID of the failed delivery log entry

Example request:

# Replace WEBHOOK_ID, LOG_ID and API_KEY
curl -X POST "https://api.lomi.africa/webhooks/YOUR_WEBHOOK_ID/logs/YOUR_LOG_ID/retry" \
  -H "X-API-Key: YOUR_API_KEY"

Automatic retries: When your endpoint rejects a delivery (non-2xx), times out from lomi.’s viewpoint, or a transient fault occurs mid-flight, the platform records the failure and may schedule additional attempts depending on HTTP semantics and plumbing. Retries are bounded: they follow exponential backoff policies on the webhook infrastructure, rather than indefinite “fire until success” loops.

Roughly speaking: 4xx client errors are usually terminal for that automated sequence (misconfiguration should be corrected on your side), while transient failures (5xx, timeouts, certain network faults) may be retried up to a bounded number of attempts with exponential backoff. See Webhook reliability: Timeouts and retries for the approximate ~4 second outbound HTTP timer, up to four attempts on some paths, up to five when delivery is queued, and the meaning of lomi_environment.

On this page