Eclipse Initiated Notifications (Webhooks)
There are certain circumstances under which Eclipse will initiate API calls to a tenant's domain. Typical examples of this are notification of transactions on a particular store-of-value within Eclipse, or in cases where the Eclipse wallet is a facade to an external SoV on the tenant's platform and and API call is needed to authorise a debit or a credit. This section will describe the typical use cases where such calls are used, how they are configured and their corresponding payloads (models).
Security
It must be noted that all calls to 3rd party platform in these use cases will have an Eclipse-Signature header, as described in the 'Encryption and Integrity section''. It is vital that all tenant endpoints implement a check to be sure all their endpoints expecting calls from Eclipse are from Eclipse before processing them.
Source IP Addresses
Callbacks / webhooks (from Eclipse) in case tenants whish to whitelist, are as follows:
79.125.40.127
54.220.185.240
18.134.2.182
3.11.195.109
For Sandbox, the source IP for callbacks / webhooks is:
34.255.93.65
Wallet Movement Notification
This webhook is triggered to a 3rd party URL for all movements on a wallet based on the wallet's configuration, as specified by it's type and type configuration. For every wallet, there is an associated wallet-type. Each wallet-type can have a configuration parameter called, 'walletMovementWebhookUrl' which can be populated with any URL that Eclipse is able to call. A screenshot illustrating this config can be seen below.
It is important to note that this API call is performed once only. Eclipse will asynchronously send the request and will not retry if the API call fails.
In some cases where the transfer uses eventual consistency (such as digital to PTS card wallets), the notification can be sent before PTS has updated its balance. To avoid this, a delay can be configured on the wallet type so that any notifications for that wallet type are delayed. This is set via 'walletMovementWebhookDelayMs' and can be set on PTS wallet types to something like 3000 so that the notification happens 3 seconds after the transaction and gives time for PTS to be aware of the transaction.
An example of the payload for this callback is as follows:
{
"transactionId": "123",
"walletId": 51588,
"type": "Cr",
"date": "2022-11-10T17:52:31.000+02:00",
"amount": 1.00,
"fee": 0,
"currency": "ZMW",
"description": "blah blah",
"authorisationCode": "CBHVYZ",
"externalId": "231409331575",
"externalUniqueId": "baff409432820bee9092f49147a704f0"
}
A description of these fields and their validation criteria are as follows:
@NotNull(message = "Value '${validatedValue}' for Transaction's transactionId is invalid: It is mandatory")
@Schema(description = "Unique identifier for the transaction generated by the wallet/card system")
protected String transactionId;
@NotNull(message = "Value '${validatedValue}' for Transaction's walletId is invalid: It is mandatory")
@Positive(message = "Value '${validatedValue}' for Transaction's walletId is invalid: It must be positive")
@Schema(description = "The walletId of the transaction")
protected long walletId;
@Schema(description = "Transaction type")
protected String type;
@Schema(description = "ISO 8601 of the transaction")
protected ZonedDateTime date;
@Schema(description = "Amount. Negative for debits, positive for credits. Amounts are in major currency unit")
protected BigDecimal amount;
@Schema(description = "Fee. The additional fee charged as part of the transaction")
protected BigDecimal fee;
@Schema(description = "Currency of the transaction. E.g. USD, ZAR, NGN etc")
protected String currency;
@Schema(description = "Closing balance in the currency of the wallet.")
protected BigDecimal balance;
@Schema(description = "Transaction description")
protected String description;
@Schema(description = "Auth code for card transactions")
protected String authorisationCode;
@Schema(description = "External identifier for the transaction which can be used for reconcilliation. Need not be unique")
protected String externalId;
@Schema(description = "The externally provided unique identifier for the transaction")
protected String externalUniqueId;
@Schema(description = "If this was a transfer then this field will contain the other walletId which was debited/credited")
protected Long otherWalletId;
@Schema(description = "Any location information about the transaction such as IP address or GPS")
protected String location;
Wallet Movement SMS and Email Notifications
Eclipse offers a straightforward way to configure SMS and Email notifications for both successful and failed wallet transactions. In the case of SMS, the message will be sent to the phone number of the user associated to the particular wallet. For email the email address of the user associated to the particular wallet will be used.
Configuration Parameters
Wallet types can be configured with parameters:
- walletMovementSMSTemplate – Specifies the global property that defines the Mustache template used for SMS notifications on successful transactions.
- failedWalletMovementSMSTemplate – Specifies the global property that defines the Mustache template used for SMS notifications on failed transactions.
- walletMovementEmailTemplate – Specifies the global property that defines the Mustache template used for Email notifications on successful transactions.
- failedWalletMovementEmailTemplate – Specifies the global property that defines the Mustache template used for Email notifications on failed transactions.
Note
Mustache templates for SMS and Email support basic text substitution. For more advanced logic (e.g. conditionals), you can use the Handlebars variant by specifying #Type:Handlebars in the template header.
The following data fields are available for use in the SMS and Email mustache templates:
Field | Description |
---|---|
amount | Amount of the related transaction |
balance | Balance of the wallet being transacted on |
currency | Currency of the related transaction |
date | Date of the transaction |
description | The description of the transaction |
externalId | The externalId field of the transaction |
externalUniqueId | The externalUniqueId field of the transaction |
location | Location of the transaction |
otherWalletId | The destination wallet ID of the transaction |
type | The transaction type |
walletId | The wallet being transacted on |
transactionId | The ID of the transaction |
Example simple mustache template for walletMovementSMSTemplate:
#UrlDataSource: wallet=/rest/v1/wallets/{{#round0}}{{data.otherWalletId}}{{/round0}}
#UrlDataSource: user=/rest/v1/users/{{#round0}}{{wallet.userId}}{{/round0}}
#To: {{user.phone1}}
Your wallet {{data.friendlyId}} has had a transaction of R{{#round0}}{{data.amount}}{{/round0}} .
Your closing balance is R{{#round0}}{{data.balance}}{{/round0}}
Example complex mustache template for walletMovementSMSTemplate:
#Type:Handlebars
#UrlDataSource: wallet=/rest/v1/wallets/{{data.walletId}}
#UrlDataSource: user=/rest/v1/users/{{wallet.userId}}
#To: {{user.phone1}}
<property>custom.sms.header.property</property>
{{#if (gt data.amount 0)}}
You recieved R{{data.amount}}. Your account's available balance is R{{data.balance}}.
{{else}}
You sent R{{data.amount}}. Your account's available balance is R{{data.balance}}.
{{/if}}
<property>custom.sms.footer.property</property>
Example simple mustache template for failedWalletMovementSMSTemplate:
#UrlDataSource: wallet=/rest/v1/wallets/{{#round0}}{{data.walletId}}{{/round0}}
#UrlDataSource: user=/rest/v1/users/{{#round0}}{{wallet.userId}}{{/round0}}
#To: {{user.phone1}}
Dear client, your payment of R{{#round0}}{{data.amount}}{{/round0}} at {{data.location}} was declined due to {{data.description}}.
Example mustache template for walletMovementEmailTemplate:
#Type: Handlebars
#UrlDataSource: wallet=/rest/v1/wallets/{{data.walletId}}
#UrlDataSource: user=/rest/v1/users/{{wallet.userId}}
#UrlDataSource: organisation=/rest/v1/organisations/{{wallet.organisationId}}
#From: ++comms.email.address++
#To: {{#if wallet.userId}}{{user.email1}}{{else}}{{organisation.email}}{{/if}}
#Subject: Payment transaction
<p></p>
Wallet Movement Authorization
Related to wallet movement notifications is the ability to send debit notifications to the tenant for authorization before the debit is processed - if the tenant does not authorize then Eclipse will roll back any ledger changes done. This gives tenants the ability to auth transactions synchronously and supports the use case where a tenant has their own ledger and they want this to mirror the Eclipse wallet ledger.
There are 2 wallet-type configuration parameters that apply to this scenario:
1. preDebitTenantAuthUrl -URL that Eclipse will do a POST to with the details of any transfer debit from the wallet and only if an HTTP response code < 300 is returned will Eclipse process the transaction. This gives tenants the ability to auth transactions synchronously. The call must return within 3 seconds as this is the timeout of the request. The tenants URL path must have eclipseauthrequest in the path.
2. preDebitTenantAuthSkipForUserId - Transfers by this userId will not result in preDebitTenantAuthUrl being called even if it is configured. Tenants can use this to not to a back-to-back auth on transactions when not required (e.g. the tenant initiated the transfers themselves)
Event Driven Notifications
In addition to the notifications mentioned above, Eclipse provides a comprehensive event-driven notification framework, enabling notifications to be triggered by any event within the ecosystem.
This can be used for a wide range of use cases:
- Dynamic customer notifications across multiple channels, based on ecosystem events.
- Streaming events to external systems (e.g., SIEM, analytics platforms).
The framework works as follows:
- Eclipse publishes events for all activities within the ecosystem into the events framework
- Third parties can also publish events for all activities
- Notification listeners and notification templates can be configured per tenant - this defines what events should trigger which notifications
- Mustache or handlebar templates can be used to send notifications
- Handlers exist for SMS, Email, Push notifications and HTTP

Prerequisites:
- Tenant config eventPublishers - (default value in global property: impl.event.publishers). This property uses regex to define which events trigger notifications for this tenant.
^(wallet\.movement\.(credit|debit)|card\.movement|proxy\.wallet\.movement|postilion\.card\.(update|expiry|pin\.update))$=com.ukheshe.eclipse.conductor.listener.EclipseEventNotificationListener
This configuration will trigger notifications for the following events:
- wallet.movement.credit
- wallet.movement.debit
- card.movement
- proxy.wallet.movement
- postilion.card.update
- postilion.card.expiry
- postilion.card.pin.update
- Tenant config events.notification.template.{event_name} - this property defines the mustache or handlebars template to send the notification. Note this supports the unified Handlebars template to dynamically send notifications based on preferences.
Example
To send dynamic notifications to a customer depending on the customer communication preference for all credit wallet movement events.
- Set tenant config impl.event.publishers to match wallet.movement.credit:
^(wallet\.movement\.(credit))$=com.ukheshe.eclipse.conductor.listener.EclipseEventNotificationListener
- Set tenant config events.notification.template.wallet.movement.credit to the template to use to send the notifications:
handlebar.template.walletMovementTemplate
- Define the handlebar template in global property handlebar.template.walletMovementTemplate:
#Type: Handlebars
#UrlDataSource: user=/rest/v1/users/{{data.userId}}
{{#if (or (eq user.communicationPreference "SMS") (eq user.communicationPreference null))}}
#SMS-Start
#To: {{user.phone1}}
This is the sample SMS. Sent by {{data.sender}}
#SMS-End
{{/if}}
{{#eq user.communicationPreference "EMAIL"}}
#Email-Start
#From: ++comms.email.address++
#To: {{user.email1}}
#Subject: dev-test-template-1
This is Sample email Template. Sent by {{data.sender}}
#Email-End
{{/eq}}
{{#eq user.communicationPreference "PUSH"}}
#PushNotification-Start
This is the PushNotification template Sent by {{data.sender}}
#PushNotification-End
{{/eq}}
Sending Events Over HTTP
The events framework can publish events not only to the notifications framework for templating and sending emails and SMS etc, but can be used in a more raw form to send events to tenants in plain JSON. A primary use case of this is to publish Eclipse events to a SIEM (Security Information and Event Management). Again the same impl.event.publishers property (or eventPublishers tenant config) can be used to indicate what events must be analysed to send to tenants. E.g.:
.*=com.ukheshe.arch.impl.event.HttpUrlPerTenantEventPublisher
Then on the tenant config, set property eventWebhookUrl:
https://mybank.com/siem/events
Event Types
Events sent over HTTP follow the structure below:
{
"eventType": {
"type": "string",
"description": "Type of the event, e.g. user.organisation.create"
},
"traceId": {
"type": "string",
"description": "Unique trace identifier for tracking the event"
},
"tenantId": {
"type": "integer",
"description": "Tenant identifier related to the event"
},
"created": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the event was created"
},
"data": {
"type": "object",
"description": "Event-specific payload"
},
"serverId": {
"type": "string",
"description": "Unique identifier of the server where the event originated"
},
"serverIp": {
"type": "string",
"format": "ipv4",
"description": "IP address of the server where the event originated"
},
"instigator": {
"type": "object",
"description": "Details of the user or system that initiated the event",
"properties": {
"identity": { "type": "string" },
"roles": {
"type": "array",
"items": { "type": "string" }
},
"positions": {
"type": "array",
"items": {}
},
"userId": { "type": "integer" },
"locale": { "type": "string" },
"remoteAddr": { "type": "string", "format": "ipv4" },
"sessionId": { "type": "string" },
"source": { "type": "string" },
"attributes": { "type": "object" },
"scope": {
"type": "array",
"items": {}
},
"system": { "type": "boolean" },
"anonymous": { "type": "boolean" }
},
"required": [
"identity",
"roles",
"userId",
"locale",
"remoteAddr",
"sessionId",
"source",
"system",
"anonymous"
]
},
"threadName": {
"type": "string",
"description": "Thread name on which the event was processed"
},
"softwareVersion": {
"type": "string",
"description": "Version of the software handling the event"
},
"associatedEntityId": {
"type": "string",
"description": "Identifier of the entity associated with the event"
},
"associatedEntityType": {
"type": "string",
"description": "Type of the entity associated with the event, e.g. organisation"
},
"schemaVersion": {
"type": "integer",
"description": "Version of the schema used for this event"
}
}
Example event for a newly created organisation:
{
"eventType": "user.organisation.create",
"traceId": "baab12a7dfbd1e1cc4f56c796706613f",
"tenantId": 11224145,
"created": "2025-08-12T10:23:17.757Z",
"data": {
"organisation": {
"organisationId": 11245917,
"name": "Test",
"phone1": "1234567890",
"status": 1,
"externalId": "e2df5a70-4461-4e38-b45c-320258b41226",
"created": "2025-08-12T10:23:18.000Z",
"lastModified": "2025-08-12T10:23:18.000Z",
"version": 0,
"bankDetails": [],
"organisationAddresses": [],
"organisationDocuments": [],
"tenantId": 11224145,
"preferences": []
}
},
"serverId": "e5b7c655-302c-4709-ad78-f867ce36b28d",
"serverIp": "172.31.34.124",
"instigator": {
"identity": "[email protected]",
"roles": [
"GLOBAL_ADMIN"
],
"positions": [],
"userId": 28603,
"locale": "en-US",
"remoteAddr": "105.233.41.30",
"sessionId": "e69a8767-ab47-44f3-8cdc-e168c41b0a31",
"source": "Auth-RP",
"attributes": {},
"scope": [],
"system": false,
"anonymous": false
},
"threadName": "v-quarkus-905",
"softwareVersion": "sandbox.6aa6dd9.2025-08-12T10-17-13",
"associatedEntityId": "11245917",
"associatedEntityType": "organisation",
"schemaVersion": 1
}
Common Event Types
Group | Event Type | Description |
---|---|---|
aws | aws.liveness.session | A liveness session has been created in AWS. |
card | card.created | A new card has been created. |
card | card.updated | A card has been updated. |
card | card.deleted | A card has been deleted. |
card | card.locked | A card has been locked. |
card | card.unlocked | A card has been unlocked. |
card | card.expired | A card has expired. |
payment | payment.created | A new payment has been created. |
payment | payment.completed | A payment has been completed. |
payment | payment.failed | A payment has failed. |
payment | payment.reversed | A payment has been reversed. |
postilion | postilion.transaction.decline.isomessage | A Postilion transaction was declined, and an ISO 8583 message was generated. |
postilion | postilion.transaction.decline | A Postilion transaction was declined. |
postilion | postilion.settlement.request | A settlement request is being sent to Postilion. |
postilion | postilion.incoming.transaction | An incoming transaction from Postilion is being processed. |
postilion | postilion.incoming.transaction.isomessage | An incoming transaction from Postilion is being processed, with ISO 8583. |
postilion | postilion.card.update.remote | A card update is being sent to Postilion. |
postilion | postilion.transaction.created | A new Postilion transaction has been created. |
postilion | postilion.transaction.completed | A Postilion transaction has been completed. |
sms | sms.send.success | An SMS message was sent successfully. |
sms | sms.send.failure | An SMS message failed to send. |
tenant | tenant.created | A new tenant has been created. |
tenant | tenant.updated | A tenant has been updated. |
tenant | tenant.deleted | A tenant has been deleted. |
user | user.user.update | A user's profile has been updated. |
user | user.created | A new user has been created. |
user | user.updated | A user has been updated. |
user | user.deleted | A user has been deleted. |
user | user.locked | A user has been locked. |
user | user.unlocked | A user has been unlocked. |
user | user.password.changed | A user's password has been changed. |
user | user.role.added | A role has been added to a user. |
user | user.role.removed | A role has been removed from a user. |
user | user.group.added | A group has been added to a user. |
user | user.group.removed | A group has been removed from a user. |
user | user.permission.added | A permission has been added to a user. |
user | user.permission.removed | A permission has been removed from a user. |
user | user.session.started | A user session has started. |
user | user.session.ended | A user session has ended. |
wallet | wallet.transfer.auth.success | A wallet transfer authorization was successful. |
wallet | wallet.transfer.auth.fail.business | A wallet transfer authorization failed for a business reason. |
wallet | wallet.transfer.auth.fail.system | A wallet transfer authorization failed for a system reason. |
wallet | wallet.mapping.create | A new wallet mapping has been created. |
wallet | wallet.created | A new wallet has been created. |
wallet | wallet.updated | A wallet has been updated. |
wallet | wallet.locked | A wallet has been locked. |
wallet | wallet.unlocked | A wallet has been unlocked. |
wallet | wallet.closed | A wallet has been closed. |
wallet | wallet.transfer.created | A wallet transfer has been created. |
wallet | wallet.transfer.completed | A wallet transfer has been completed. |
wallet | wallet.transfer.failed | A wallet transfer has failed. |
Note
The data field contains the object relevant to the event.
For example:
- user.created → the data field will contain the user object.
For detailed specifications of event objects, please contact [email protected].
Consolidated Callbacks for Unsolicited Deposits/Payments
For longer running transactions like payments, topups and withdrawals callbacks on transaction completion including transaction details and status are important for tenants to receive. Typically for tenant initiated payments, withdrawals or topups a callback URL can be specified by the tenant when initiating that transaction and the full transaction object is posted to that URL when the transaction completes.
However there are certain transactions that are not tenant initiated, that tenants would also like to be notified on - for example an EFT payment into a wallet or a QR code payment into a wallet.
To be notified on any unsolicited deposits/payments (e.g. an EFT payment is done into a wallet, or a QR code is paid into a wallet), the completed transaction object can be posted to a URL. This URL can be set at an organisation level by including an attachment of type consolidatedCallbacks that specifies the notification URL. If the payment is for a wallet associated to that organisation then the notification will be sent to that URL. Alternatively this can be set at a tenant level by setting tenant configuration consolidatedCallbacks to the specific URL.
POST /eclipse-conductor/rest/v1/tenants/{tenantId}/organisations/{organisationId}/attachments
{
"attachmentType": "consolidatedCallbacks",
"mediaType": "text/plain",
"type": "consolidatedCallbacks",
"info": "https://webhook.site/91368c6c-6581-4434-9500-06ae5d36694b"
}
An example of the payload for the callback when an EFT payment is done is as follows:
{
"externalUniqueId": "+VNp9bmNHwnjjskFPTWInv2aUD2rg/HYGjqPRHWQ8vU=",
"walletId": 3129,
"amount": 10.000000000,
"created": "2023-11-01T09:26:00Z",
"paymentReference": "JVAHS6G3",
"fee": 0.10000000000000000000000000000,
"description": "JVAHS6G3",
"retrievalReferenceNumber": 0,
"paymentType": "ATM",
"paymentId": 10491,
"customerId": 3761,
"currency": "ZAR",
"status": "SUCCESSFUL"
}
An example of the payload for the callback when a QR payment is done is as follows:
{
"paymentId": 74881,
"externalUniqueId": "MP-IN-4589207-SUCCESS",
"status": "SUCCESSFUL",
"amount": 100.000000000,
"description":"QR Code Paymeny",
"currency": "ZAR",
"additionalFields": [],
"acceptedCardSchemes": [],
"cardPhone": "9726999279",
"phone": "9726999279",
"acceptedPaymentMechanisms": [],
"paymentType": "CARD",
"authCode": " DEBUG",
"retrievalReferenceNumber": 809260,
"created": "2023-11-22T10:26:59.000Z",
"paymentData": "6244523710",
"paymentInstrumentInfo": {
"cardBin": "424242",
"cardType": "CREDIT",
"cardLast4": "4242",
"cardName": "TESTING",
"cardPhone": "9726999279"
},
"fee": 0E-9,
"paymentReference": "7165124d-5e53-49b6-b644-b3474c8d6ccb",
"walletId": 204149,
"organisationId": 11869,
"associatedPaymentId": 74878,
"gatewayTransactionId": "MP-IN-4589207-SUCCESS"
}
Updated 6 days ago