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
2xxstatus 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
eventIdfor
domain idempotency, and logsvix-idfor Svix delivery troubleshooting. - Out-of-order. Events can arrive out of order. Use
occurredAtwhen 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-299responses 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 type | Destination | Sent when |
|---|---|---|
rica.customer.linked | Partner and MVNO global | A RICA customer/entity is linked to a SIM. |
rica.registration.submitted | Partner and MVNO global | A self-service or agent-assisted RICA registration is submitted. |
rica.registration.verified | Partner and MVNO global | A RICA registration reaches a verified state. |
rica.registration.failed | Partner and MVNO global | A verification run fails the RICA registration. |
rica.registration.rejected | Partner and MVNO global | A RICA registration is manually rejected. |
rica.registration.ops_queue | Partner and MVNO global | A RICA registration needs manual intervention. |
dma.received | Partner and MVNO global | A valid DMA payload is ingested and linked to Nova state. |
dma.completed | Partner and MVNO global | DMA orchestration completes and the account is active. |
dma.ops_queue | Partner and MVNO global | DMA orchestration needs manual intervention. |
dma.failed | Partner and MVNO global | DMA is permanently rejected or hard-failed. |
transaction.completed | Activating partner, selling partner, and MVNO global | A commission-reportable transaction completes. |
app_user.wallet.created | MVNO global | An 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
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_draftself_service_draft_submitagent_assisted_draft_submit
RICA registration lifecycle events
These event types share the same payload shape:
rica.registration.submittedrica.registration.verifiedrica.registration.failedrica.registration.rejectedrica.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 type | eventId suffix | Typical registrationStatus | Current source values |
|---|---|---|---|
rica.registration.submitted | submitted | PENDING_SPEND_TRIGGER | self_service_draft_submit, agent_assisted_draft_submit |
rica.registration.verified | verified | VERIFIED | partner_portal_submit, verification_run_transition |
rica.registration.failed | failed | FAILED | verification_run_transition |
rica.registration.rejected | rejected | REJECTED | manual_rejection |
rica.registration.ops_queue | ops_queue | OPS_QUEUE | verification_run_transition |
DMA payloads
All DMA event types share the same payload shape:
dma.receiveddma.completeddma.ops_queuedma.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 type | eventId suffix | Typical status | Notes |
|---|---|---|---|
dma.received | received | PROCESSING | Local DMA state was linked and committed. |
dma.completed | completed | COMPLETED | Account activation completed. |
dma.ops_queue | ops_queue | OPS_QUEUE | failedStep names the orchestration step when available. |
dma.failed | failed | FAILED | errorMessage describes the hard failure when available. |
Transaction payloads
transaction.completed
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
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.
Updated 3 months ago