Data Streams - Document Verification

When a new document is pushed to your target_url, you may want to verify that the document is actually coming from Meltwater.

For each call to your target_url we provide a signature header X-Hub-Signature. This signature is a hash of the JSON payload that we are sending you. More specifically this signature is a HMAC encoded, 40 character long hexadecimal sha1 with a sha1= prefix.

Example: X-Hub-Signature: sha1=9065c86cefbd8f0cc82f888f8c520b7f7c0b5157

Exchanging the shared secret

In order to use a signature, we need to establish a shared secret between you (the consumer) and the Meltwater API. We provide two approaches for how to exchange the shared secret:

(1) You provide the shared secret when creating a data stream

Use the header X-Hook-Secret with the POST /v3/hooks endpoint. The shared secret is limited to minimum 16 and maximum 64 characters.

(2) We auto-generate a shared secret when you are creating a data stream

If you don’t provide the header X-Hook-Secret, we will auto-generate a random secret for you.

In both approaches, you will be able to obtain the shared secret from the response header X-Hook-Secret when using the POST /v3/hooks endpoint.

Verifying the signature

Now that you know how to obtain the signature and the secret, you need to verify the signature in your service.

You do so by calculating the signature of the payload yourself, using the secret as a decryption key. If your calculated signature matches the X-Hub-Signature that you received, then you know that the messages clearly comes from the Meltwater API.

Here some examples in different programming languages of how to calculate the signature yourself:

Using Benthos/Bloblang

For a more comprehensive example, see our example Benthos config file.

pipeline:
  processors:
    # Signature verification
    - bloblang: |
        # Delete the message if the HOOK_SECRET env variable is set and the signatures don't match
        root = if "${HOOK_SECRET:''}" != "" && meta("X-Hub-Signature") != "sha1=%s".format(content().hash("hmac_sha1","${HOOK_SECRET:''}").encode("hex")) {
            deleted()
        }

Using Ruby

$ irb
require 'openssl'
secret = '11114f34565bd3b2247d123762de0234231eb181'
payload = '[{"my": "json_payload"}]'
OpenSSL::HMAC.hexdigest('sha1', secret, payload)
=> "9065c86cefbd8f0cc82f888f8c520b7f7c0b5157"

Using Elixir

$ iex
secret = "11114f34565bd3b2247d123762de0234231eb181"
payload = "[{\"my\": \"json_payload\"}]"
signature = :sha |> :crypto.hmac(secret, payload) |> Base.encode16 |> String.downcase
"9065c86cefbd8f0cc82f888f8c520b7f7c0b5157"

Using Bash

SECRET='11114f34565bd3b2247d123762de0234231eb181'
PAYLOAD='[{"my": "json_payload"}]'
echo -n "$PAYLOAD" | openssl dgst -sha1 -hmac "$SECRET" -hex
"9065c86cefbd8f0cc82f888f8c520b7f7c0b5157"