ARTICLE AD BOX
I'm having problem comparing a sha256 for a webhook validation from this documentation:
api doc: https://docs.api.delyva.com/?version=latest#a207fce6-44cb-43f6-aa30-e3234aedcb3d
api guide: https://delyva.com/my/blog/kb/developer-guide-to-integrating-with-delyva-api/#integration-models
webhook validation (sample from api provider/delyva):
const express = require('express'); const crypto = require('crypto'); const app = express(); app.use(express.json()); app.post('/webhooks/delyva', (req, res) => { const payload = JSON.stringify(req.body); const signature = req.headers['x-delyvax-hmac-sha256']; const expected = crypto.createHmac('sha256', process.env.DELYVA_API_SECRET) .update(payload).digest('base64'); if (signature !== expected) return res.status(401).send('Invalid signature'); // handle event... res.send('ok'); }); app.listen(3000, () => console.log('listening'));My code in CodeIgniter4 (Sandbox):
public function postTracking(): ResponseInterface { // Set your API secret $apiSecret = 'dx6ed8365ad507451595c151b6b61e3dbdcfbb987f'; // Get the raw POST body $parsedBody = $this->request->getJSON(); $payload = json_encode($parsedBody, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); // Get the signature from headers $signature = $this->request->getHeaderLine('X-Delyvax-Hmac-Sha256'); // Calculate expected signature $expected = base64_encode( hash_hmac('sha256', $payload, $apiSecret, true) ); // Handle event... return $this->respond([ 'status' => 'success', 'sign' => $signature, 'computed' => $expected, ]); }Variable for your reference:
$apiSecret = 'dx6ed8365ad507451595c151b6b61e3dbdcfbb987f'; X-Delyvax-Hmac-Sha256 = f8vncFdUbn+STSRVnS3oa6Rr+50tEGevZaYc7LrRncw=json body(payload request):
{ "id": "cfa3d6ff-3d37-4d8f-8d14-e7a064d520ab", "companyId": "9e0aed8a-5c67-42a4-82b6-e01bf7687f31", "userId": "06a8b310-fcfd-11f0-9353-1fee8250c30f", "customerId": 126004, "serviceId": 3234, "serviceCode": "GDEXMY-WM", "price": { "amount": 0, "currency": "MYR" }, "revenue": { "amount": 0, "currency": "MYR" }, "commission": { "amount": 0, "currency": "MYR" }, "distance": { "unit": "km", "value": 1 }, "weight": { "unit": "kg", "value": 14.54 }, "cod": { "amount": 0, "currency": "MYR" }, "consignmentNo": "DX0010241MY", "paymentTerm": "debit", "itemType": "PARCEL", "itemTypeId": null, "dimension": { "unit": "cm", "width": 0, "height": 0, "length": 0 }, "statusCode": 100, "status": "created", "createdAt": "2026-03-26T05:15:58.847Z", "updatedAt": "2026-03-26T05:16:00.720Z", "deletedAt": null, "failedReason": "", "promoCode": "", "vehicleId": null, "invoiceId": null, "discountPrice": "0.00", "discount": "0.00", "personnelId": null, "note": "", "extId": "", "extIdType": "", "serviceAddon": null, "rating": null, "ratingNote": "", "agentCommission": null, "billing": { "city": "", "name": "gobisnes solutions enterprise", "email": "[email protected]", "phone": "60103213012", "state": "", "mobile": "", "unitNo": "", "country": "MY", "address1": "", "address2": "", "postcode": "" }, "paymentMethodId": null, "cancelledReason": null, "source": "", "promoValue": "0.00", "cron": 0, "metadata": { "taskId": 468160 }, "personnel": {}, "commodityId": "", "pluginId": null, "surcharge": "0.00", "requestPickup": null, "insurance": { "amount": 0, "currency": "MYR" }, "extTrackUrl": "", "smsRate": "0.00", "smsCredit": 0, "smsUsed": 0, "nanoId": "0mSeliGu", "extId2": "", "tip": "0.00", "ip": "43.205.48.39", "platformFee": "0.00", "branded": false, "pickupId": null, "codStatus": null, "referenceNo": null, "multiPcs": null, "multiPcsMaxWeight": 0, "codDueAt": null, "waRate": "0.00", "waCredit": 0, "waUsed": 0, "rtsReason": null, "pickupManifestId": null, "quoteId": null, "subConsignmentNo": [], "labelS3Key": null, "claimStatus": null, "claimNotes": null, "claimCreatedAt": null, "slaPickupMin": null, "slaDeliveryMin": null, "slaCodProcessingMin": null, "slaCodSettlementMin": null, "slaClaimsMin": null, "deliveryDueAt": null, "pickupDueAt": null, "waypoint": [ { "id": "wp_t44MJUwwaLhKVF6qVGDcMg", "orderId": "cfa3d6ff-3d37-4d8f-8d14-e7a064d520ab", "no": 1, "inventory": [ { "name": "Apparel", "type": "PARCEL", "price": { "amount": 60, "currency": "MYR" }, "action": "P", "weight": { "unit": "kg", "value": 14.54 }, "quantity": 1, "dimension": { "unit": "cm", "width": 10, "height": 33, "length": 30 }, "description": "-" } ], "contact": { "city": "Kuala Lumpur", "name": "GoBisnes Solutions Enterprise", "coord": { "lat": "3.07591", "lon": "101.70577" }, "phone": "60103213012", "state": "Kuala Lumpur", "unitNo": "", "country": "MY", "address1": "Lot 33, Jln Pauh Punggah", "address2": "Kg Malaysia Raya", "postcode": "57100", "sortCode": "MW01" }, "type": "PICKUP", "description": "-", "status": "pending", "note": "-", "scheduledAt": "2026-03-26T05:15:59.023Z", "createdAt": "2026-03-26T05:15:58.847Z", "updatedAt": "2026-03-26T05:16:00.720Z", "deletedAt": null, "cash": { "amount": 0, "currency": "MYR" }, "placeId": null, "actualScheduledAt": null, "startAt": "2026-03-27T02:00:59.023Z", "actualStartAt": null, "extId": "" }, { "id": "wp_kdJYATv24G6rk7RLHWZrfB", "orderId": "cfa3d6ff-3d37-4d8f-8d14-e7a064d520ab", "no": 2, "inventory": [ { "name": "Apparel", "type": "PARCEL", "price": { "amount": 60, "currency": "MYR" }, "action": "D", "weight": { "unit": "kg", "value": 14.54 }, "quantity": 1, "dimension": { "unit": "cm", "width": 10, "height": 33, "length": 30 }, "description": "-" } ], "contact": { "city": "Sungai Besi", "name": "Nik", "phone": "60132017736", "state": "Kuala Lumpur", "unitNo": "", "country": "MY", "address1": "Jalan kenangan", "address2": "", "postcode": "12345", "sortCode": "MW01" }, "type": "DROPOFF", "description": "-", "status": "pending", "note": "-", "scheduledAt": "2026-03-26T14:00:59.023Z", "createdAt": "2026-03-26T05:15:58.847Z", "updatedAt": "2026-03-26T05:16:00.720Z", "deletedAt": null, "cash": { "amount": 0, "currency": "MYR" }, "placeId": null, "actualScheduledAt": null, "startAt": "2026-03-26T05:17:59.969Z", "actualStartAt": null, "extId": "" } ], "orderId": "cfa3d6ff-3d37-4d8f-8d14-e7a064d520ab", "consignmentNos": [ "DX0010241MY" ] }What I'm trying to achieve is, to ensure my php code perform the logic exactly as the node sample there. Im using php. the webhook sample on calculation is based on node.
the variable sample is from the request sent by webhook server.
Right now, i still figuring out how to solve this. the hash/signature out still not match.
UPDATE: SOLUTION (apparently secret key is not api key for delyva integration. just received it from their dev team):
// Get raw request body (for signature verification) $rawBody = $this->request->getBody(); // Get signature from header $signature = $this->request->getHeaderLine('X-Delyvax-Hmac-Sha256'); // Parse JSON data $jsonData = json_decode($rawBody, true); // Get API secret from merchant record $apiSecret = 'YOUR SECRET KEY'; // Verify signature (if signature header is present) if ($signature) { $expected = hash_hmac('sha256', $rawBody, $apiSecret, true); $expectedBase64 = base64_encode($expected); if (!hash_equals($expectedBase64, $signature)) { log_message('error', 'Invalid webhook signature for order: ' . $courierOrderId); return $this->failUnauthorized('Invalid signature'); } log_message('info', 'Webhook signature verified for order: ' . $courierOrderId); } else { log_message('warning', 'No signature header received for order: ' . $courierOrderId); // You may choose to reject or accept based on your security needs }