Webhooks

Webhooks

Nova delivers outbound business events through Svix. Each partner or MVNO
global webhook configuration maps to a Svix application, and Svix POSTs the
event payload to the endpoints configured for that application.

The HTTP request body is the Nova domain payload for the event type. It is not
wrapped in the Nova API response envelope, and it no longer uses the old
{ "type": "...", "data": { ... }, "version": 1 } shape.

Configuring endpoints

Partner admins can configure partner-scoped webhooks with
POST /partners/{partnerUuid}/webhooks/configure. MVNO admins can configure
the global MVNO webhook application with POST /mvno/webhooks/configure.
Both endpoints return a temporary Svix dashboard URL and token when configured.

In the Svix dashboard:

  • Add an HTTPS endpoint that you control.
  • Choose the event types the endpoint should receive.
  • Save the endpoint signing secret; you need it to verify deliveries.
  • Use the testing and replay tools before relying on a production handler.

Your endpoint must:

  • Use HTTPS with a valid certificate.
  • Verify the Svix signature before trusting the JSON body.
  • Respond with any 2xx status within 15 seconds.
  • Be idempotent; Svix retries failed deliveries and Nova uses stable event IDs.
  • Ignore unknown fields so Nova can add non-breaking fields later.

Delivery semantics

  • At-least-once. A delivery can be retried. Use the payload eventId for
    domain idempotency, and log svix-id for Svix delivery troubleshooting.
  • Out-of-order. Events can arrive out of order. Use occurredAt when order
    matters.
  • Retries. Svix retries with exponential backoff after failures: immediately,
    5 seconds, 5 minutes, 30 minutes, 2 hours, 5 hours, 10 hours, then another
    10 hours.
  • Success. Svix treats HTTP 200-299 responses as success. Redirects,
    4xx, 5xx, timeouts, and network errors are failures.

Signature verification

Each Svix delivery includes these headers:

svix-id: msg_...
svix-timestamp: 1714214130
svix-signature: v1,...

Use the endpoint signing secret from the Svix dashboard. Verify against the
raw request body before JSON parsing. The recommended path is the official
Svix library:

import { Webhook } from 'svix';

export function verifyNovaWebhook(
  rawBody: string | Buffer,
  headers: Record<string, string | string[] | undefined>,
  secret: string,
) {
  const webhook = new Webhook(secret);
  return webhook.verify(rawBody, headers);
}

For manual verification, Svix signs the exact string
<svix-id>.<svix-timestamp>.<raw-request-body> with HMAC-SHA256 using the
base64 secret bytes after the whsec_ prefix. Reject stale timestamps according
to your own tolerance window.

Svix verification reference:
How to verify webhooks.

Event types

Event typeDestinationSent when
rica.customer.linkedPartner and MVNO globalA RICA customer/entity is linked to a SIM.
rica.registration.submittedPartner and MVNO globalA self-service or agent-assisted RICA registration is submitted.
rica.registration.verifiedPartner and MVNO globalA RICA registration reaches a verified state.
rica.registration.failedPartner and MVNO globalA verification run fails the RICA registration.
rica.registration.rejectedPartner and MVNO globalA RICA registration is manually rejected.
rica.registration.ops_queuePartner and MVNO globalA RICA registration needs manual intervention.
dma.receivedPartner and MVNO globalA valid DMA payload is ingested and linked to Nova state.
dma.completedPartner and MVNO globalDMA orchestration completes and the account is active.
dma.ops_queuePartner and MVNO globalDMA orchestration needs manual intervention.
dma.failedPartner and MVNO globalDMA is permanently rejected or hard-failed.
transaction.completedActivating partner, selling partner, and MVNO globalA commission-reportable transaction completes.
app_user.wallet.createdMVNO globalAn app user wallet account is created.

Event types are Svix message metadata used for endpoint subscription filtering
and dashboard logs. Nova's current HTTP body contains the domain payload shown
below, not a separate eventType field.

RICA payloads

All RICA payloads use public UUID fields. Internal database IDs are not exposed.
Fields that are not available are sent as null.

rica.customer.linked

{
  "eventId": "rica-registration:RICA-20260623-0001:customer-linked",
  "occurredAt": "2026-06-23T10:15:30.123Z",
  "registrationUuid": "8e58e1a3-89e7-4cd7-a88c-69b90cf7f0ad",
  "registrationReference": "RICA-20260623-0001",
  "registrationStatus": "DRAFT",
  "channel": "PARTNER_PORTAL",
  "source": "partner_portal_draft",
  "entityUuid": "03721da1-f67a-45a1-9765-280a22842525",
  "entity": {
    "uuid": "03721da1-f67a-45a1-9765-280a22842525",
    "idNumber": "8001015009087",
    "idType": "SA_ID",
    "country": "South Africa",
    "dateOfBirth": "1980-01-01"
  },
  "simCardUuid": "3928c811-7aa3-4f66-91e5-c67e89a0c1e6",
  "iccid": "8927000000000000000",
  "customerPhoneNumber": "27821234567",
  "partnerUuid": "8adcfec1-5afd-4626-b0bc-6a4a8bc3c9bb",
  "partnerUserUuid": "09af9f0c-b6a7-4b65-8b32-bc2277fbeb2d"
}

source values currently used for this event:

  • partner_portal_draft
  • self_service_draft_submit
  • agent_assisted_draft_submit

RICA registration lifecycle events

These event types share the same payload shape:

  • rica.registration.submitted
  • rica.registration.verified
  • rica.registration.failed
  • rica.registration.rejected
  • rica.registration.ops_queue
{
  "eventId": "rica-registration:RICA-20260623-0001:verified",
  "occurredAt": "2026-06-23T10:20:30.123Z",
  "registrationUuid": "8e58e1a3-89e7-4cd7-a88c-69b90cf7f0ad",
  "registrationReference": "RICA-20260623-0001",
  "registrationStatus": "VERIFIED",
  "channel": "PARTNER_PORTAL",
  "source": "partner_portal_submit",
  "entityUuid": "03721da1-f67a-45a1-9765-280a22842525",
  "simCardUuid": "3928c811-7aa3-4f66-91e5-c67e89a0c1e6",
  "iccid": "8927000000000000000",
  "customerPhoneNumber": "27821234567",
  "partnerUuid": "8adcfec1-5afd-4626-b0bc-6a4a8bc3c9bb",
  "partnerUserUuid": "09af9f0c-b6a7-4b65-8b32-bc2277fbeb2d"
}

Event-specific status and source values:

Event typeeventId suffixTypical registrationStatusCurrent source values
rica.registration.submittedsubmittedPENDING_SPEND_TRIGGERself_service_draft_submit, agent_assisted_draft_submit
rica.registration.verifiedverifiedVERIFIEDpartner_portal_submit, verification_run_transition
rica.registration.failedfailedFAILEDverification_run_transition
rica.registration.rejectedrejectedREJECTEDmanual_rejection
rica.registration.ops_queueops_queueOPS_QUEUEverification_run_transition

DMA payloads

All DMA event types share the same payload shape:

  • dma.received
  • dma.completed
  • dma.ops_queue
  • dma.failed
{
  "eventId": "dma:54184238-cf75-4dff-a30f-cef890f9837e:completed",
  "occurredAt": "2026-06-23T10:30:30.123Z",
  "dmaEventUuid": "54184238-cf75-4dff-a30f-cef890f9837e",
  "status": "COMPLETED",
  "iccid": "8927000000000000000",
  "msisdn": "27821234567",
  "simCardUuid": "3928c811-7aa3-4f66-91e5-c67e89a0c1e6",
  "accountUuid": "724c9b3a-a8f5-4c2f-8515-a4f06c8aab8c",
  "partnerUuid": "8adcfec1-5afd-4626-b0bc-6a4a8bc3c9bb",
  "mnoTransactionId": "MNO-123456",
  "sourceIdentifier": "SOA",
  "failedStep": null,
  "errorMessage": null
}

Event-specific status values:

Event typeeventId suffixTypical statusNotes
dma.receivedreceivedPROCESSINGLocal DMA state was linked and committed.
dma.completedcompletedCOMPLETEDAccount activation completed.
dma.ops_queueops_queueOPS_QUEUEfailedStep names the orchestration step when available.
dma.failedfailedFAILEDerrorMessage describes the hard failure when available.

Transaction payloads

transaction.completed

{
  "eventId": "transaction:7a1d856b-9a0f-40a2-b5c8-4d137e6db4a7:completed",
  "occurredAt": "2026-06-23T10:45:30.123Z",
  "transactionUuid": "7a1d856b-9a0f-40a2-b5c8-4d137e6db4a7",
  "transactionTypeCode": "AIRTIME",
  "amount": 150,
  "direction": "SALE",
  "accountUuid": "724c9b3a-a8f5-4c2f-8515-a4f06c8aab8c",
  "activatingPartnerUuid": "8adcfec1-5afd-4626-b0bc-6a4a8bc3c9bb",
  "sellingPartnerUuid": "d8d3d60e-c5e0-4416-a2a5-7a574b5cbf55",
  "sellingAgentPartnerUuid": null
}

Nova emits this event only for transaction types that are configured to publish
commission-processing events.

Wallet payloads

app_user.wallet.created

{
  "eventId": "app-user-wallet:41a8255c-3615-4ac9-bf8a-42284833355c:created",
  "occurredAt": "2026-06-23T10:50:30.123Z",
  "appUserUuid": "c147306a-cdc9-4b7a-a61c-a747c84d7e3b",
  "walletAccountUuid": "41a8255c-3615-4ac9-bf8a-42284833355c",
  "walletProfileId": "profile_123",
  "walletAccountNumber": "902100000001",
  "status": "ACTIVE",
  "entity": {
    "uuid": "03721da1-f67a-45a1-9765-280a22842525",
    "idNumber": "8001015009087",
    "idType": "SA_ID",
    "country": "South Africa",
    "birthdate": "1980-01-01"
  }
}

entity is null when the app user does not have a linked FICA entity.


Did this page help you?