Table of Contents
Introduction
This guide provides comprehensive instructions for integrating with our webhook system to receive real-time updates about various events in your application. It covers subscription creation, payload structure, security measures, and best practices for a reliable integration.
Prerequisites
To effectively use our webhook system, ensure you have the following:
A publicly accessible HTTPS endpoint to receive webhook events
Capability to verify HMAC-SHA256 signatures
Familiarity with RESTful APIs and JSON.
Terminal application version should be on 1.11.0 or higher.
Webhook Subscription
Creating a Subscription
Follow the instructions at Suscription Management
A unique secret will be generated for each subscription
Retrieve this secret from the subscription menu after creation
Endpoint Requirements
Support POST HTTP method
Use HTTPS for secure communication
Please note that only the POST method is supported for webhooks.
Webhook Event Payload
When an event occurs, we send a POST request to your specified endpoint. The request includes the following headers and a JSON body.
Request Headers
{ "user-agent": "ReactorNetty/1.1.9", "content-length": "631", "accept": "application/json", "accept-encoding": "gzip", "content-type": "application/json", "moniepoint-webhook-id": "b15ec58f-fa1f-4abb-8329-efaef8aa2bef", "moniepoint-webhook-signature": "WMfZqwVUKvjo6eY/W4qh5FT2M6Wb/2nSuC3aVMbpSNA=", "moniepoint-webhook-timestamp": "1728651860073", "x-forwarded-for": "141.147.77.31", "x-forwarded-host": "wenhookdemo.free.beeceptor.com", "x-forwarded-proto": "https" }
Header | Description |
---|---|
| A unique identifier for the webhook event |
| The time when the webhook was created, in epoch milliseconds |
| A Base64encoded HMAC-SHA256 signature used to verify the payload's integrity |
Request Body
The body of the webhook POST request contains a JSON object with details about the transaction. Below is an example payload:
{ "data": { "amount": 25300, "businessId": 10, "customFields": { "Invoice ID": "82674382" }, "responseCode": "09", "terminalSerial": "P260678997653", "businessOwnerId": 100, "responseMessage": "Airtime Purchase Pending", "transactionTime": "2024-10-10T09:32:57.916+0100", "transactionType": "AIRTIME", "merchantReference": "", "transactionStatus": "PENDING", "transactionReference": "ATP|2MPT0073|183849658930533333120", "retrievalReferenceNumber": "" }, "eventId": "59630e16-34f0-40ee-b5c3-a3d66e71ca41", "subject": { "domain": "CHANNELS", "resource": "business", "resourceId": "10" }, "createdAt": "2024-10-11T14:04:20.051330639", "eventType": "V1_POS_AIRTIME_TRANSACTION" }
Payload Field Descriptions
Field | Type | Description | Example |
---|---|---|---|
data.amount | Integer | The amount involved in the transaction, represented in smallest currency units (e.g., kobo for NGN). | 25300 (Twenty-five thousand, three hundred naira) |
data.businessId | Integer | Unique identifier of the business associated with the transaction. | 10 |
data.customFields | Object | A map of custom key-value pairs related to the transaction, such as Invoice ID. | { "Invoice ID": "82674382" } |
data.responseCode | String | Code representing the response status of the transaction. |
|
data.terminalSerial | String | The serial number of the POS terminal used for the transaction. |
|
data.businessOwnerId | Integer | Unique identifier of the owner of the business. | 100 |
data.responseMessage | String | Message providing additional information about the transaction response. |
|
data.transactionTime | String (ISO 8601) | Timestamp indicating when the transaction occurred, in ISO 8601 format. |
|
data.transactionType | String | The type of transaction being processed. | "AIRTIME" |
data.merchantReference | String | Reference provided by the merchant for the transaction (can be empty if not applicable). | ““ |
data.transactionStatus | String | Current status of the transaction. |
|
data.transactionReference | String | A reference number used for transaction retrieval (may be empty if not provided). This is always unique per transaction |
|
data.retrievalReferenceNumber | String | A reference number for retrieving the transaction, if applicable. | ““ |
eventId | String(UUID) | This is the unique identifier of the events. |
|
subject.domain | String | The domain related to the event, e.g., "CHANNELS". |
|
subject.resoureceId | String | Unique identifier of the resource subject associated with the event, such as the businessId. |
|
subject.resourceId | String | Unique identifier of the resource subject associated with the event, such as the businessId. |
|
createdAt | String (ISO 8601) | Timestamp indicating when the event was created, in ISO 8601 format. |
|
eventType | String | Type of the event that was triggered, e.g., "V1_POS_AIRTIME_TRANSACTION". |
|
Available Event Types
Event Type | Description |
---|---|
V1_POS_WITHDRAWAL_TRANSACTION | Event triggered for a POS withdrawal transaction. |
V1_POS_PURCHASE_TRANSACTION | Event for purchase transactions made via POS. |
V1_POS_CARD_TRANSFER_TRANSACTION | Event for transferring funds using a card at POS. |
V1_POS_BILL_PAYMENT_TRANSACTION | Event for bill payments made through POS. |
V1_POS_TRANSFER_TRANSACTION | Event for pos transfers initiated from POS. |
V1_TRANSFER_TRANSACTION | General transfer transaction event. |
V1_POS_COLLECTION_TRANSACTION | Event triggered when a merchant monnify collection is made through POS |
V1_POS_PAY_CODE_TRANSACTION | Event for transactions using a merchant monnify pay code at POS. |
V1_POS_AIRTIME_TRANSACTION | Event for airtime purchase transactions through POS |
V1_POS_BOOM_TRANSACTION | Event related to Boom-specific transactions via POS. |
More event types may be added in the future. Ensure your system can accommodate new events.
Verifying Webhook Signatures
To ensure the authenticity of webhook requests, verify the signature included in the moniepoint-webhook-signature
header.
Verification Process
Concatenate
moniepoint-webhook-id
,moniepoint-webhook-timestamp
, and the request body using '"__"' as a delimiter.Generate an HMAC-SHA256 hash using your webhook secret and the concatenated string.
Compare the generated signature with the
moniepoint-webhook-signature
header to ensure integrity.
Code Sample for Signature Verification
const crypto = require('crypto'); /** * Validates the webhook payload signature against the expected signature. * * @param {string} webhookId - The unique identifier for the webhook. * @param {string} timestamp - The timestamp of the webhook event. * @param {string} requestBody - The body of the request as a JSON string. * @param {string} signature - The signature received in the webhook request. * @param {string} secretKey - The secret key used to generate the signature. * @returns {boolean} - Returns true if the signature is valid, false otherwise. * * @example * const webhookId = "your_webhook_id"; * const timestamp = "timestamp_value"; * const requestBody = '{"key": "value"}'; * const expectedSignature = "HvzIH3TaI0jFiMPbcuH4NblQ9Mmz+WKzodD1dpFlMHM="; * const secretKey = "your_secret_key"; * * const isValid = isPayloadSignatureValid(webhookId, timestamp, requestBody, expectedSignature, secretKey); * console.log(`Signature is valid: ${isValid}`); */ const isPayloadSignatureValid = (webhookId, timestamp, requestBody, signature, secretKey) => { const data = `${webhookId}__${timestamp}__${requestBody}`; const expectedSignature = crypto.createHmac('sha256', secretKey).update(data).digest('base64'); return signature === expectedSignature; };
Online Verification Tool
To manually verify the HMAC-SHA256 hash, you can use the https://www.devglan.com/online-tools/hmac-sha256-online
Data: Use the concatenated string: moniepoint-webhook-id__moniepoint-webhook-timestamp__requestBody.
Use your secret key.
Algorithm: Select HMAC-SHA256.
This will generate the hash, which should match the moniepoint-webhook-signature header if the webhook request is valid.
Retry Mechanism
Our webhook system implements an automatic retry mechanism for failed deliveries, using exponential backoff.
Maximum Retries: The system will retry a given number of times. If the maximum number of attempts is reached without a successful delivery, the system will back off, ceasing further retries.
Conditions for Retry
A non2xx status code is returned by the subscriber's endpoint.
The request times out.
Handling Retries
Implement idempotent processing for incoming webhooks using the
moniepoint-webhook-id
to avoid duplicate processing.Log failed attempts and responses for debugging purposes.
Best Practices
Idempotency: Use the moniepoint-webhook-id from the header to ensure that the processing of each webhook event is idempotent. Since the same event will always have the same moniepoint-webhook-id, store and check this ID before processing. If an event with the same moniepoint-webhook-id has already been processed, ignore subsequent attempts to prevent duplication.
Asynchronous Processing: Respond quickly with a 2xx status code and process the event in a background job to prevent timeouts.
Security: Keep your endpoint_url and webhook_secret secure. Regenerate secrets if a compromise is suspected.
Logging: Log webhook events and their processing status to troubleshoot issues effectively.
Timeout: Set a reasonable timeout (e.g., 10 seconds) on your endpoint to avoid long delays.
Testing: Use a staging environment to test webhooks before moving to production.
By following these guidelines, you can ensure a secure and reliable integration with our webhook system. If you have any questions or require further assistance, please don't hesitate to reach out.
Contact Support:
support@moniepoint.com