GuidesAPI ReferenceChangelog
Log In

# Introduction

Pinwheel signs all webhook events that it sends to your endpoints. This allows you to verify that events were sent by Pinwheel, not a third party.

## Verifying Pinwheel is the Sender

Each webhook event includes the `x-pinwheel-signature` header. This header contains a signature in the form `{signature_version}={signature_digest}`. The `{signature_version}` should be `v2`. The `{signature_digest}` is a hex-encoded string generated using the [signature algorithm](🔗).

You should use a string comparison function that is safe against [timing attacks](🔗) when comparing signatures. Otherwise, it may be possible for an attacker to ascertain information about secret values such as your API Secret. See the examples below for constant-time comparison functions in various languages.

### Signature Algorithm

The signature algorithm is specified in pseudo-code. We also include [example implementations](🔗) in various languages. To verify that your implementation is compliant, please leverage the [test suite](🔗) at the bottom of this page.

  1. Retrieve the `{timestamp}`, stored in the `x-timestamp` header.

  2. Retrieve the `{raw_request_body}`.

    1. This should be the un-parsed, un-decoded bytes from the HTTP request body. No UTF-8 decoding or JSON processing should have occurred on this value. We use the raw bytes to avoid per-language/framework processing differences.

    2. See the examples below for how to retrieve this in various web frameworks.

  3. Set `{key}` as your [API Secret](🔗).

  4. Construct the `{msg_prefix}` by UTF-8 encoding the string `{signature_version}:{timestamp}:`. Notice the trailing `:`.

  5. Construct the `{msg}` by byte-concatenating `{msg_prefix}` and `{raw_request_body}`.

  6. Calculate the `{raw_digest}` by running [HMAC-SHA256](🔗) with `{key}` on `{msg}`.

  7. Produce `{signature_digest}` by hex-encoding the `{raw_digest}`.

## Example Implementations

## Test Suite

We've put together test cases to cover a variety of payload structures your signature implementation may need to accommodate. To be prepared for the set of valid webhook events our system may send your endpoints your signature algorithm implementation must be compliant with the first 4 (text-based) test cases and should be compliant with the last (non-text) test case.

We used the following parameters as fixed inputs when generating each `{signature_digest}`:

  • `{timestamp}` = `860860860`

  • `{key}` = the string `TEST_KEY` UTF-8 encoded, i.e. `544553545f4b4559` in hex, i.e. `"TEST_KEY".encode("utf-8")` in Python.

You should download the test case files directly, using a command such as `curl --remote-name $TEST_CASE_FILE_URL`, to avoid introducing newlines or other extraneous characters into them which will affect their binary contents. Similarly, when viewing test cases make sure to use a read-only mechanism, such as `cat $TEST_CASE_FILE`, to avoid affecting their binary contents.

Test Case DescriptionTest Case File, `{raw_request_body}``{signature_digest}`
Base JSON payload[test_data_1_base.json](🔗)`42fb9eba200e821d4de63667f5a30f7e1b83609b135e148e26ce01eef2aa6ba8`
Base JSON with keys in a different order[test_data_2_reordered.json](🔗)`032457677fee3481d60bdd3896554762f0d66ad277fead31da997ead50a645f6`
Base JSON with all whitespace removed[test_data_3_no_whitespace.json](🔗)`46df1cb08a430de9c808de2b5209dea4a4fafca5a8a3a0c5af0ca705c62f3c32`
Base JSON with characters (emoji) outside the Latin-1 character set[test_data_4_non_latin1.json](🔗)`1d694c72f8d5e00ef80f63bb3963f1097a02c4fb79cc7624eb42d6d566655d2d`
Binary, non-text data (image)[test_data_5_non_text.jpg](🔗)`3ebb15420de1f53cb20e0660c57952dabff41d9c38f718fb3ddec6140199f1d8`

Please contact [[email protected]](🔗) for access to our [Developer Dashboard](🔗).