Description: Review examples of Webhooks below.
For an in-depth guide on Online Payments Webhooks, view the Online Payments Webhooks Guide.
Signature Verification Example
The following example was written in TypeScript to illustrate the signature verification process. The example uses a simple Express HTTPS server and includes replay attack mitigation.
import crypto from 'crypto';
import express from 'express';
import fs from 'fs';
import https from 'https';
import path from 'path';
// Private key shared with Shift4
const PRIVATE_KEY = 'YOUR_PRIVATE_KEY';
function parseSignatureHeader(signatureHeader: string): { timestamp: number; signature: string } {
const [timestampPart, signaturePart] = signatureHeader.split(',');
const [, timestamp] = timestampPart.split('=');
const [, signature] = signaturePart.split('=');
return { timestamp: parseInt(timestamp, 10), signature };
}
function isValidTimeFrame(timestamp: number, toleranceMinutes = 5): boolean {
const difference = +new Date() - timestamp;
const tolerance = toleranceMinutes * 60 * 1000;
return difference <= tolerance;
}
async function startServer(): Promise<https.Server> {
const [key, cert] = await Promise.all([
fs.promises.readFile(path.join(__dirname, 'key.pem')),
fs.promises.readFile(path.join(__dirname, 'cert.pem')),
]);
const app = express();
app.use(express.json());
app.use('/webhooks', async (req, res) => {
// Ensure header exists once
if (!req.headers['shift4-signature'] || typeof req.headers['shift4-signature'] !== 'string') {
throw new Error('Malformed or missing signature header');
}
// Parse the timestamp and signature from the header
const { timestamp, signature } = parseSignatureHeader(req.headers['shift4-signature']);
// Rebuild the signature to verify
const contents = `${timestamp}:${JSON.stringify(req.body)}`;
const verifySignature = crypto.createHmac('sha256', PRIVATE_KEY).update(contents).digest('hex');
// Ensure the signature is valid and the request happened in the allotted time frame
if (verifySignature !== signature || !isValidTimeFrame(timestamp)) {
// Unauthorized
res.sendStatus(401);
return;
}
// At this point the request is valid. Since we'll need to reply within 5 seconds,
// you'll want to send the payload to another process for handling before replying
// with a 2XX status code.
res.sendStatus(200);
});
const server = https.createServer({ key, cert }, app);
server.listen(443);
return server;
}
(async function start(): Promise<void> {
await startServer();
})();
Webhook Type: Dispute Example
{
"transactionSequenceNumber": "abc123",
"acquirerMerchantId": "xyz789",
"processingDate": "xyz789",
"transactionDate": "xyz789",
"cardExpirationDate": "2501",
"fundingDate": "abc123",
"authCode": "xyz789",
"transactionTypeCode": "xyz789",
"posEntryMode": "xyz789",
"cardAccountNumber": "xyz789",
"transactionAmount": 9.87,
"authorizedAmount": 123.11,
"cashbackAmount": 987,
"merchantCategoryCode": "xyz789",
"cardBrand": "xyz789",
"cardProductType": "xyz789",
"batchNumber": "abc123",
"networkReferenceNumber": "abc123",
"authorizationSource": "abc123",
"authorizationDate": DateTime,
"mailPhoneIndicator": "abc123",
"debitCreditIndicator": "abc123",
"catIndicator": "abc123",
"invoiceNumber": "abc123",
"terminalId": "xyz789",
"currencyCode": "abc123",
"authCurrencyCode": "xyz789",
"visaProductCode": "xyz789",
"dbaName": "abc123",
"avsResponseCode": "abc123",
"cvv2ResponseCode": "xyz789",
"transactionTime": DateTime,
"rejectReason": "xyz789",
"authResponseCode": "xyz789",
"providerPath": "abc123",
"estimatedBankDate": DateTime,
"associationNumber": "xyz789",
"adjustmentAmount": "abc123",
"adjustmentType": "abc123",
"adjustmentDescription": "abc123",
"regulatedIndicator": "abc123",
"billingDescriptor": "abc123",
"issuingBank": "abc123",
"issuingBankCountry": "abc123",
"fundsTransferGroup": "xyz789",
"interchangeCode": "xyz789",
"fileNumber": "xyz789",
"airlineTicketNum": "xyz789",
"token": "abc123",
"receiptNumber": "xyz789",
"disputeRecordNumber": "xyz789",
"disputeAmount": 123,
"chargebackReferenceNumber": "xyz789",
"initialReportDate": "xyz789",
"originalTransactionDate": "abc123",
"chargebackActionCode": "abc123",
"issuerAdjustmentReasonCode": "abc123",
"resolutionTo": "xyz789",
"recordType": "xyz789",
"firstChargebackFlag": "xyz789"
}
Webhook Type: Refund Example
{
"transactionSequenceNumber": "abc123",
"acquirerMerchantId": "xyz789",
"processingDate": "xyz789",
"transactionDate": "xyz789",
"cardExpirationDate": "2501",
"fundingDate": "abc123",
"authCode": "xyz789",
"transactionTypeCode": "xyz789",
"posEntryMode": "xyz789",
"cardAccountNumber": "xyz789",
"transactionAmount": 9.87,
"authorizedAmount": 123.11,
"cashbackAmount": 987,
"merchantCategoryCode": "xyz789",
"cardBrand": "xyz789",
"cardProductType": "xyz789",
"batchNumber": "abc123",
"networkReferenceNumber": "abc123",
"authorizationSource": "abc123",
"authorizationDate": DateTime,
"mailPhoneIndicator": "abc123",
"debitCreditIndicator": "abc123",
"catIndicator": "abc123",
"invoiceNumber": "abc123",
"terminalId": "xyz789",
"currencyCode": "abc123",
"authCurrencyCode": "xyz789",
"visaProductCode": "xyz789",
"dbaName": "abc123",
"avsResponseCode": "abc123",
"cvv2ResponseCode": "xyz789",
"transactionTime": DateTime,
"rejectReason": "xyz789",
"authResponseCode": "xyz789",
"providerPath": "abc123",
"estimatedBankDate": DateTime,
"associationNumber": "xyz789",
"adjustmentAmount": "abc123",
"adjustmentType": "abc123",
"adjustmentDescription": "abc123",
"regulatedIndicator": "abc123",
"billingDescriptor": "abc123",
"issuingBank": "abc123",
"issuingBankCountry": "abc123",
"fundsTransferGroup": "xyz789",
"interchangeCode": "xyz789",
"fileNumber": "xyz789",
"airlineTicketNum": "xyz789",
"token": "abc123",
"receiptNumber": "xyz789"
}
Webhook Type: Sale Example
{
"transactionSequenceNumber": "abc123",
"acquirerMerchantId": "xyz789",
"processingDate": "xyz789",
"transactionDate": "xyz789",
"cardExpirationDate": "2501",
"fundingDate": "abc123",
"authCode": "xyz789",
"transactionTypeCode": "xyz789",
"posEntryMode": "xyz789",
"cardAccountNumber": "xyz789",
"transactionAmount": 9.87,
"authorizedAmount": 123.11,
"cashbackAmount": 987,
"merchantCategoryCode": "xyz789",
"cardBrand": "xyz789",
"cardProductType": "xyz789",
"batchNumber": "abc123",
"networkReferenceNumber": "abc123",
"authorizationSource": "abc123",
"authorizationDate": DateTime,
"mailPhoneIndicator": "abc123",
"debitCreditIndicator": "abc123",
"catIndicator": "abc123",
"invoiceNumber": "abc123",
"terminalId": "xyz789",
"currencyCode": "abc123",
"authCurrencyCode": "xyz789",
"visaProductCode": "xyz789",
"dbaName": "abc123",
"avsResponseCode": "abc123",
"cvv2ResponseCode": "xyz789",
"transactionTime": DateTime,
"rejectReason": "xyz789",
"authResponseCode": "xyz789",
"providerPath": "abc123",
"estimatedBankDate": DateTime,
"associationNumber": "xyz789",
"adjustmentAmount": "abc123",
"adjustmentType": "abc123",
"adjustmentDescription": "abc123",
"regulatedIndicator": "abc123",
"billingDescriptor": "abc123",
"issuingBank": "abc123",
"issuingBankCountry": "abc123",
"fundsTransferGroup": "xyz789",
"interchangeCode": "xyz789",
"fileNumber": "xyz789",
"airlineTicketNum": "xyz789",
"token": "abc123",
"receiptNumber": "xyz789"
}
Comments
0 comments
Please sign in to leave a comment.