Webhook Integration
Open Market uses webhooks to notify your application when a payment is successful. Only successful transactions trigger webhook notifications. This real-time notification system allows you to automatically update your application’s state, fulfill orders, or trigger other business processes.
Webhook Authentication
Webhooks are authenticated using your private key. Each webhook request includes a signature in the opm-signature
header that you should validate to ensure the webhook is legitimate. This prevents unauthorized parties from sending fake webhook events to your endpoint.
Webhook Payload
When a payment is successful, we’ll send a POST request to your configured webhook URL with the following JSON payload:
Handling Webhooks
Here’s an example of how to handle and validate webhooks:
const crypto = require ( 'crypto' ) ;
const express = require ( 'express' ) ;
const app = express ( ) ;
app. use ( express. json ( ) ) ;
function validateSignature ( payload, signature, privateKey ) {
const hmac = crypto. createHmac ( 'sha256' , privateKey) ;
const expectedSignature = hmac. update ( JSON . stringify ( payload) ) . digest ( 'hex' ) ;
return crypto. timingSafeEqual (
Buffer . from ( signature) ,
Buffer . from ( expectedSignature)
) ;
}
const ProcessedWebhook = mongoose. model ( 'ProcessedWebhook' , {
transaction_id : { type : String , unique : true } ,
processed_at : { type : Date , default : Date . now }
} ) ;
app. post ( '/webhook' , async ( req, res ) => {
try {
const signature = req. headers [ 'opm-signature' ] ;
const payload = req. body ;
const privateKey = process. env . OPM_PRIVATE_KEY ;
if ( ! validateSignature ( payload, signature, privateKey) ) {
console . error ( 'Invalid signature for transaction:' , payload. transaction_id ) ;
return res. status ( 400 ) . send ( 'Invalid signature' ) ;
}
const existingWebhook = await ProcessedWebhook . findOne ( {
transaction_id : payload. transaction_id
} ) ;
if ( existingWebhook) {
console . log ( 'Duplicate webhook received:' , payload. transaction_id ) ;
return res. status ( 200 ) . send ( 'Webhook already processed' ) ;
}
res. status ( 200 ) . send ( 'Webhook received' ) ;
await processWebhookData ( payload) ;
await ProcessedWebhook . create ( {
transaction_id : payload. transaction_id
} ) ;
} catch ( error) {
console . error ( 'Webhook processing error:' , error) ;
if ( ! res. headersSent ) {
res. status ( 200 ) . send ( 'Webhook received with errors' ) ;
}
await storeFailedWebhook ( payload, error) ;
}
} ) ;
async function processWebhookData ( payload ) {
const {
transaction_id,
reference,
meta_data,
status,
amount,
buyer_email,
buyer_name
} = payload;
await Orders . findOneAndUpdate (
{ reference } ,
{
payment_status : status,
payment_confirmed : true ,
transaction_id
}
) ;
if ( meta_data?. order_id) {
await fulfillOrder ( meta_data. order_id ) ;
}
await sendPaymentConfirmation ( {
email : buyer_email,
name : buyer_name,
amount,
reference
} ) ;
}
async function storeFailedWebhook ( payload, error ) {
await FailedWebhooks . create ( {
transaction_id : payload. transaction_id ,
payload : payload,
error : error. message ,
created_at : new Date ( ) ,
retry_count : 0
} ) ;
}
const PORT = process. env . PORT || 3000 ;
app. listen ( PORT , ( ) => {
console . log ( ` Webhook server listening on port ${ PORT } ` ) ;
} ) ;
Best Practices for Webhooks
Webhook Use Cases
Testing Webhooks
During development, you can use tools like ngrok to test webhooks locally:
Then update your webhook URL in the API Settings with the ngrok URL.
Remember to update your webhook URL to your production URL before going live.