Signed Requests

There are some cases when Spreedly needs to pass you important information over a channel that isn't naturally secure. An example of this are the callbacks you receive after an offsite purchase. In these cases we sign any critical fields so you can verify that the values really are coming from us.

Your signing secret

Each environment has its own unique signing secret, which you can find by clicking the environment's "Signing secret" button in the Environment menu in Spreedly's merchant portal. .


The "Signing secret" section will display your current signing secret as well as the ability to regenerate it if you'd like to periodically roll this value.



We will always sign requests from that environment using the Signing Secret; Access Secrets are never used for signing.

Verifying the signature

When we sign a message, it will contain a signature field that tells you exactly which fields are signed, and in what order. Here's Ruby code that validates a signature for a (trimmed down) transaction:

require 'openssl'
require 'nokogiri'

def signature_for(secret, xml)
  document = Nokogiri::XML(xml)
  algorithm = document.xpath("//transaction/signed/algorithm").text

  fields = document.xpath("//transaction/signed/fields").text.split(" ")

  values = fields.collect do |field|
    document.xpath("//transaction/#{field}").text
  end

  signature_data = values.join("|")
  OpenSSL::HMAC.hexdigest(
    OpenSSL::Digest.new(algorithm),
    secret,
    signature_data
  )
end

signature = signature_for(
  "4ziASKWGGV1zdrUbSN6vq2CjjDPw2hzJSvsGLhxces1aORBKKsRyJwb8DfGQ6J3q",
  %(
    <transactions>
      <transaction>
        <amount type="integer">100</amount>
        <on_test_gateway type="boolean">false</on_test_gateway>
        <created_at type="datetime">2021-04-07T20:35:10Z</created_at>
        <updated_at type="datetime">2021-04-07T20:35:11Z</updated_at>
        <currency_code>USD</currency_code>
        <succeeded type="boolean">true</succeeded>
        <state>succeeded</state>
        <token>5AG4P7FPjlfIA6aED6AgZvUEehx</token>
        <transaction_type>OffsitePurchase</transaction_type>
        <order_id nil="true"></order_id>
        <ip nil="true"></ip>
        <callback_url>https://example.com/handle_callback</callback_url>
        <signed>
          <signature>f02c1189622670b0c5ab970f0f5b65e6d91cf817</signature>
          <fields>amount callback_url created_at currency_code ip on_test_gateway order_id state succeeded token transaction_type updated_at</fields>
          <algorithm>sha1</algorithm>
        </signed>
      </transaction>
    </transactions>
  )
)

This matches the signature included in the transaction, validating it as being from someone who knows the API secret.