Skip to content

Commit 6a4cbd6

Browse files
feat: add support for validating webhook signatures inter-768
1 parent de05eb7 commit 6a4cbd6

File tree

7 files changed

+168
-1
lines changed

7 files changed

+168
-1
lines changed

README.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,32 @@ All URIs are relative to your region's base URL.
174174
| Europe | https://eu.api.fpjs.io |
175175
| Asia | https://ap.api.fpjs.io |
176176

177+
## Webhook Signing
178+
179+
This SDK provides utility method for verifying the HMAC signature of the incoming webhook request.
180+
You can use below code to verify signature:
181+
182+
```php
183+
<?php
184+
185+
// Your webhook signing secret.
186+
$webhookSecret = "secret";
187+
188+
// Request data. In real life scenerio this will be the body of incoming request
189+
$webhookData = "data";
190+
191+
// Value of the "fpjs-event-signature" header.
192+
$webhookHeader = "v1=1b2c16b75bd2a870c114153ccda5bcfca63314bc722fa160d690de133ccbb9db";
193+
194+
$isValidWebhookSign = \Fingerprint\ServerAPI\Webhook\WebhookVerifier::checkHeader($webhookHeader, $webhookData, $webhookSecret);
195+
196+
if(!$isValidWebhookSign) {
197+
fwrite(STDERR, sprintf("Webhook signature verification failed\n"));
198+
exit(1);
199+
}
200+
201+
```
202+
177203
## Endpoints
178204

179205

@@ -278,6 +304,10 @@ Class | Method | HTTP request | Description
278304
- [Sealed](docs/Sealed/Sealed.md)
279305
- [DecryptionKey](docs/Sealed/DecryptionKey.md)
280306

307+
## Documentation for webhooks
308+
309+
- [Webhook](docs/Webhook.md)
310+
281311
## Tests
282312

283313
To run the unit tests:

docs/Webhook.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Webhook
2+
3+
## **CheckHeader**
4+
5+
> Fingerprint\ServerAPI\Webhook\WebhookVerifier::checkHeader(string $header, string $data, string $secret): bool
6+
7+
Verifies the HMAC signature extracted from the "fpjs-event-signature" header of the incoming request. This is a part of the webhook signing process, which is available only for enterprise customers.
8+
If you wish to enable it, please [contact our support](https://fingerprint.com/support).
9+
10+
### Required Parameters
11+
12+
| Name | Type | Description | Notes |
13+
|------------|------------|------------------------------------------------------------|-------|
14+
| **header** | **string** | Value of the "fpjs-event-signature" header. | |
15+
| **data** | **string** | Body of the request from which above header was extracted. | |
16+
| **secret** | **string** | Your generated secret used to sign the request. | |

run_checks.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,17 @@
5757
exit(1);
5858
}
5959

60+
$webhookSecret = "secret";
61+
$webhookData = "data";
62+
$webhookHeader = "v1=1b2c16b75bd2a870c114153ccda5bcfca63314bc722fa160d690de133ccbb9db";
63+
$isValidWebhookSign = \Fingerprint\ServerAPI\Webhook\WebhookVerifier::checkHeader($webhookHeader, $webhookData, $webhookSecret);
64+
if($isValidWebhookSign) {
65+
fwrite(STDOUT, sprintf("\n\nVerified webhook signature\n"));
66+
} else {
67+
fwrite(STDERR, sprintf("\n\nWebhook signature verification failed\n"));
68+
exit(1);
69+
}
70+
6071
// Enable the deprecated ArrayAccess return type warning again if needed
6172
error_reporting(error_reporting() | E_DEPRECATED);
6273

scripts/generate.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ java -jar ./bin/swagger-codegen-cli.jar generate -t ./template -l php -i ./res/f
6565

6666
mv -f src/README.md ./README.md
6767
mv -f src/composer.json composer.json
68-
find ./docs -type f ! -name "DecryptionKey.md" ! -name "Sealed.md" -exec rm {} +
68+
find ./docs -type f ! -name "DecryptionKey.md" ! -name "Sealed.md" ! -name "Webhook.md" -exec rm {} +
6969
mv -f src/docs/* ./docs
7070

7171
if [ -z "$GITHUB_ACTIONS" ]; then

src/Webhook/WebhookVerifier.php

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
namespace Fingerprint\ServerAPI\Webhook;
4+
5+
final class WebhookVerifier
6+
{
7+
public static function checkHeader(string $header, string $data, string $secret): bool
8+
{
9+
$signatures = explode(',', $header);
10+
foreach ($signatures as $signature) {
11+
$parts = explode('=', $signature);
12+
if (2 === count($parts) && 'v1' === $parts[0]) {
13+
$hash = $parts[1];
14+
if (self::checkSignature($hash, $data, $secret)) {
15+
return true;
16+
}
17+
}
18+
}
19+
20+
return false;
21+
}
22+
23+
private static function checkSignature(string $signature, string $data, string $secret): bool
24+
{
25+
$hash = hash_hmac('sha256', $data, $secret);
26+
27+
return hash_equals($hash, $signature);
28+
}
29+
}

template/README.mustache

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,32 @@ All URIs are relative to your region's base URL.
184184
| Europe | https://eu.api.fpjs.io |
185185
| Asia | https://ap.api.fpjs.io |
186186
187+
## Webhook Signing
188+
189+
This SDK provides utility method for verifying the HMAC signature of the incoming webhook request.
190+
You can use below code to verify signature:
191+
192+
```php
193+
<?php
194+
195+
// Your webhook signing secret.
196+
$webhookSecret = "secret";
197+
198+
// Request data. In real life scenerio this will be the body of incoming request
199+
$webhookData = "data";
200+
201+
// Value of the "fpjs-event-signature" header.
202+
$webhookHeader = "v1=1b2c16b75bd2a870c114153ccda5bcfca63314bc722fa160d690de133ccbb9db";
203+
204+
$isValidWebhookSign = \Fingerprint\ServerAPI\Webhook\WebhookVerifier::checkHeader($webhookHeader, $webhookData, $webhookSecret);
205+
206+
if(!$isValidWebhookSign) {
207+
fwrite(STDERR, sprintf("Webhook signature verification failed\n"));
208+
exit(1);
209+
}
210+
211+
```
212+
187213
## Endpoints
188214

189215

@@ -226,6 +252,10 @@ Class | Method | HTTP request | Description
226252
- [Sealed](docs/Sealed/Sealed.md)
227253
- [DecryptionKey](docs/Sealed/DecryptionKey.md)
228254

255+
## Documentation for webhooks
256+
257+
- [Webhook](docs/Webhook.md)
258+
229259
## Tests
230260

231261
To run the unit tests:

test/WebhookVerifierTest.php

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?php
2+
3+
namespace Fingerprint\ServerAPI;
4+
5+
use Fingerprint\ServerAPI\Webhook\WebhookVerifier;
6+
use PHPUnit\Framework\TestCase;
7+
8+
class WebhookVerifierTest extends TestCase
9+
{
10+
private $secret = "secret";
11+
private $data = "data";
12+
13+
public function testWithValidSignature()
14+
{
15+
$validHeader = "v1=1b2c16b75bd2a870c114153ccda5bcfca63314bc722fa160d690de133ccbb9db";
16+
$result = WebhookVerifier::checkHeader($validHeader, $this->data, $this->secret);
17+
$this->assertTrue($result, "With valid signature");
18+
}
19+
20+
public function testWithInvalidHeader()
21+
{
22+
$result = WebhookVerifier::checkHeader("v2=invalid", $this->data, $this->secret);
23+
$this->assertFalse($result, "With invalid header");
24+
}
25+
26+
public function testWithHeaderWithoutVersion()
27+
{
28+
$result = WebhookVerifier::checkHeader("invalid", $this->data, $this->secret);
29+
$this->assertFalse($result, "With header without version");
30+
}
31+
32+
public function testWithEmptyHeader()
33+
{
34+
$result = WebhookVerifier::checkHeader("", $this->data, $this->secret);
35+
$this->assertFalse($result, "With empty header");
36+
}
37+
38+
public function testWithEmptySecret()
39+
{
40+
$validHeader = "v1=1b2c16b75bd2a870c114153ccda5bcfca63314bc722fa160d690de133ccbb9db";
41+
$result = WebhookVerifier::checkHeader($validHeader, $this->data, "");
42+
$this->assertFalse($result, "With empty secret");
43+
}
44+
45+
public function testWithEmptyData()
46+
{
47+
$validHeader = "v1=1b2c16b75bd2a870c114153ccda5bcfca63314bc722fa160d690de133ccbb9db";
48+
$result = WebhookVerifier::checkHeader($validHeader, "", $this->secret);
49+
$this->assertFalse($result, "With empty data");
50+
}
51+
}

0 commit comments

Comments
 (0)