3D Secure Support

3D Secure is the name for a protocol established by Visa and MasterCard that adds two-factor authentication to credit card transactions on those networks. While avoided by merchants whenever possible (since it adds a huge barrier to customers completing a transaction), and basically unused in the US, it is sometimes a requirement for some non-US based merchants.

Two things are important to keep in mind about the 3D Secure flow on Spreedly:

  1. 3D Secure is completely optional, even for gateways that support it. You must flag transactions (details below) in order for Spreedly to even attempt 3D secure on them. Similarly, even if a transaction is flagged, it will be processed regardless if run on a gateway that doesn’t support 3D Secure.
  2. You must support our asynchronous transaction flow (also used for Offsite Payments, details below) in order for it to work. It’s as similar as possible to the normal workflow, but you can’t just “flip a switch” and have it start working.

Supported Gateways

We support 3D Secure at the following gateways:

Initiate a purchase

Given an existing gateway and payment method in Spreedly, you simply invoke the transaction withe the attempt_3dsecure property set to true:


curl https://core.spreedly.com/v1/gateways/LlkjmEk0xNkcWrNixXa1fvNoTP4/purchase.xml \
  -u 'Ll6fAtoVSTyVMlJEmtpoJV8S:RKOCG5D8D3fZxDSg504D0IxU2XD4Io5VXmyzdCtTivHFTTSy' \
  -H 'Content-Type: application/xml' \
  -d '<transaction>
        <attempt_3dsecure>true</attempt_3dsecure>
        <amount>100</amount>
        <currency_code>USD</currency_code>
        <payment_method_token>3avVHNDDGH474GHnLCSAOqTeIH7</payment_method_token>
        <redirect_url>http://example.com/handle_redirect</redirect_url>
        <callback_url>http://example.com/handle_callback</callback_url>
    </transaction>'

Note that just as with offsite payments, the redirect_url and callback_url are required fields. These will be unused unless 3D Secure processing is actually triggered. More on those below.

Core will return a response that includes the transaction as usual:


<transaction>
  <on_test_gateway type="boolean">true</on_test_gateway>
  <created_at type="dateTime">2016-08-11T17:36:22Z</created_at>
  <updated_at type="dateTime">2016-08-11T17:36:22Z</updated_at>
  <succeeded type="boolean">false</succeeded>
  <state>pending</state>
  <token>HyqHT54kwN3kzeyvEzoHfTScZfX</token>
  <transaction_type>Purchase</transaction_type>
  <order_id nil="true"/>
  <ip nil="true"/>
  <description nil="true"/>
  <email nil="true"/>
  <merchant_name_descriptor nil="true"/>
  <merchant_location_descriptor nil="true"/>
  <gateway_specific_fields nil="true"/>
  <gateway_specific_response_fields>
  </gateway_specific_response_fields>
  <gateway_transaction_id nil="true"/>
  <amount type="integer">100</amount>
  <currency_code>USD</currency_code>
  <retain_on_success type="boolean">false</retain_on_success>
  <payment_method_added type="boolean">false</payment_method_added>
  <message key="messages.transaction_pending">Pending</message>
  <gateway_token>8dvNMSuDr3kTlMIQxbkIaE5KNlX</gateway_token>
  <shipping_address>
    <name>Bob Smith</name>
    <address1 nil="true"/>
    <address2 nil="true"/>
    <city nil="true"/>
    <state nil="true"/>
    <zip nil="true"/>
    <country nil="true"/>
    <phone_number nil="true"/>
  </shipping_address>
  <response>
    <success type="boolean">true</success>
    <message>Checked enrollment status</message>
    <error_code></error_code>
    <checkout_url nil="true"/>
    <created_at type="dateTime">2016-08-11T17:36:22Z</created_at>
    <updated_at type="dateTime">2016-08-11T17:36:22Z</updated_at>
  </response>
  <api_urls>
    <callback_conversations>https://core.spreedly.com/v1/callbacks/WxptIyqjA9kiLgrNdoc5RDSvykY/conversations.xml</callback_conversations>
  </api_urls>
  <payment_method>
    <token>BEQPHOXCDFgvb7bkkWn4FN1podc</token>
    <created_at type="dateTime">2016-08-11T17:36:22Z</created_at>
    <updated_at type="dateTime">2016-08-11T17:36:22Z</updated_at>
    <email nil="true"/>
    <data nil="true"/>
    <storage_state>cached</storage_state>
    <test type="boolean">true</test>
    <last_four_digits>3886</last_four_digits>
    <first_six_digits>455676</first_six_digits>
    <card_type>visa</card_type>
    <first_name>Bob</first_name>
    <last_name>Smith</last_name>
    <month type="integer">2</month>
    <year type="integer">2020</year>
    <address1 nil="true"/>
    <address2 nil="true"/>
    <city nil="true"/>
    <state nil="true"/>
    <zip nil="true"/>
    <country nil="true"/>
    <phone_number nil="true"/>
    <company nil="true"/>
    <full_name>Bob Smith</full_name>
    <eligible_for_card_updater nil="true"/>
    <shipping_address1 nil="true"/>
    <shipping_address2 nil="true"/>
    <shipping_city nil="true"/>
    <shipping_state nil="true"/>
    <shipping_zip nil="true"/>
    <shipping_country nil="true"/>
    <shipping_phone_number nil="true"/>
    <payment_method_type>credit_card</payment_method_type>
    <errors>
    </errors>
    <verification_value>XXX</verification_value>
    <number>XXXX-XXXX-XXXX-3886</number>
    <fingerprint>e26a5eae05b7b9b1bd0219242938d5a748f7</fingerprint>
  </payment_method>
  <callback_url>http://example.com:3044/spreedly</callback_url>
  <redirect_url>http://example.com/handle_redirect</redirect_url>
  <checkout_url nil="true"/>
  <checkout_form>
    <![CDATA[
<form action="https://core.spreedly.com/test/8dvNMSuDr3kTlMIQxbkIaE5KNlX/auth/HyqHT54kwN3kzeyvEzoHfTScZfX" method="POST">
  <div>
    <input name="PaReq" value="" type="hidden"/>
    <input name="MD" value="" type="hidden"/>
    <input name="TermUrl" value="https://core.spreedly.com/transaction/HyqHT54kwN3kzeyvEzoHfTScZfX/redirect" type="hidden"/>
    <input name="Complete" value="Authorize Transaction" type="submit"/>
  </div>
</form>
]]>
  </checkout_form>
  <setup_response>
    <success type="boolean">true</success>
    <message>Checked enrollment status</message>
    <error_code></error_code>
    <checkout_url nil="true"/>
    <created_at type="dateTime">2016-08-11T17:36:22Z</created_at>
    <updated_at type="dateTime">2016-08-11T17:36:22Z</updated_at>
  </setup_response>
</transaction>

This is where the process diverges from a regular credit card payment. Note the state field of the transaction: it is set to pending. We’ve also received back a status code of 202, which indicates that this transaction isn’t complete - rather it needs further processing in order to actually collect payment.

To do that further processing, what you do depends on what is returned in the checkout_url and checkout_form fields:

  • If a checkout_url is returned, you need to redirect the customer’s browser to it. Doing so will land the customer on the offsite page they will use to authorize payment.
  • If a checkout_form is returned, you need to render and submit it. You can also parse it (it is always a valid XHTML fragment) and build your own form with the same action and input fields.

If both checkout_url and checkout_form are returned, you can use whichever you prefer.

In the case of the Test gateway, a checkout_form is always returned, so we render it, submit it, and the browser lands on this page:

Test gateway 3D secure page

Note that in the case of a real gateway, this will not be a page on spreedly.com, but rather will be somewhere offsite.

Finalizing a purchase

Once the customer completes payment, their browser will be sent back to the redirect_url that you specified when creating the transaction above.


http://example.com/handle_redirect?transaction_token=2tLQS3ebpsCBYvtNtgc2kXXXkL

Note the extra field tacked on to the redirect_url; the transaction_token allows you to distinguish which transaction this redirect is in regards to. You can then lookup details on the transaction as usual, and see exactly what happened:


curl https://core.spreedly.com/v1/transactions/2tLQS3ebpsCBYvtNtgc2kXXXkL.xml \
  -u 'Ll6fAtoVSTyVMlJEmtpoJV8S:RKOCG5D8D3fZxDSg504D0IxU2XD4Io5VXmyzdCtTivHFTTSy'

<transaction>
  <on_test_gateway type="boolean">false</on_test_gateway>
  <created_at type="dateTime">2015-01-08T16:00:02-05:00</created_at>
  <updated_at type="dateTime">2015-01-08T16:00:03-05:00</updated_at>
  <succeeded type="boolean">true</succeeded>
  <state>succeeded</state>
  <token>SUp3x15f095FvImHf0trucKbxH</token>
  <transaction_type>Purchase</transaction_type>
  <order_id nil="true"/>
  <ip nil="true"/>
  <description nil="true"/>
  <email nil="true"/>
  <merchant_name_descriptor nil="true"/>
  <merchant_location_descriptor nil="true"/>
  <gateway_specific_fields nil="true"/>
  <gateway_specific_response_fields nil="true"/>
  <gateway_transaction_id nil="true"/>
  <amount type="integer">100</amount>
  <currency_code>USD</currency_code>
  <retain_on_success type="boolean">false</retain_on_success>
  <payment_method_added type="boolean">false</payment_method_added>
  <message key="messages.transaction_succeeded">Succeeded!</message>
  <gateway_token>8XJtbE1p4NTZ6fFqwwn0GrkjEmW</gateway_token>
  <response>
    <success type="boolean">true</success>
    <message>Succeeded</message>
    <avs_code nil="true"/>
    <avs_message nil="true"/>
    <cvv_code nil="true"/>
    <cvv_message nil="true"/>
    <pending type="boolean">false</pending>
    <result_unknown type="boolean">false</result_unknown>
    <error_code nil="true"/>
    <error_detail nil="true"/>
    <cancelled type="boolean">false</cancelled>
    <fraud_review type="boolean">false</fraud_review>
    <created_at type="dateTime">2015-01-08T16:00:03-05:00</created_at>
    <updated_at type="dateTime">2015-01-08T16:00:03-05:00</updated_at>
  </response>
  <api_urls>
    <callback_conversations>https://core.spreedly.com/v1/callbacks/XjCHPJad2pDbIKKq2fOJezXmxiM/conversations.xml</callback_conversations>
  </api_urls>
  <payment_method>
    <token>AtPnCOmX1usgoJdoMJvDsEcZkgZ</token>
    <created_at type="dateTime">2015-01-08T16:00:02-05:00</created_at>
    <updated_at type="dateTime">2015-01-08T16:00:03-05:00</updated_at>
    <email nil="true"/>
    <data nil="true"/>
    <storage_state>used</storage_state>
    <test type="boolean">true</test>
    <last_four_digits>3886</last_four_digits>
    <first_six_digits>455676</first_six_digits>
    <card_type>visa</card_type>
    <first_name>Bob</first_name>
    <last_name>Smith</last_name>
    <month type="integer">2</month>
    <year type="integer">2020</year>
    <address1 nil="true"/>
    <address2 nil="true"/>
    <city nil="true"/>
    <state nil="true"/>
    <zip nil="true"/>
    <country nil="true"/>
    <phone_number nil="true"/>
    <full_name>Bob Smith</full_name>
    <eligible_for_card_updater type="boolean">true</eligible_for_card_updater>
    <shipping_address1 nil="true"/>
    <shipping_address2 nil="true"/>
    <shipping_city nil="true"/>
    <shipping_state nil="true"/>
    <shipping_zip nil="true"/>
    <shipping_country nil="true"/>
    <shipping_phone_number nil="true"/>
    <payment_method_type>credit_card</payment_method_type>
    <errors>
    </errors>
    <verification_value></verification_value>
    <number>XXXX-XXXX-XXXX-3886</number>
  </payment_method>
  <callback_url>http://127.0.0.1:3044</callback_url>
  <redirect_url>http://example.com/handle_redirect</redirect_url>
  <checkout_url nil="true"/>
  <checkout_form>
    <![CDATA[
<form action="https://core.spreedly.com/test/8XJtbE1p4NTZ6fFqwwn0GrkjEmW/auth/SUp3x15f095FvImHf0trucKbxH" method="POST">
  <div>
    <input name="PaReq" value="" type="hidden"/>
    <input name="MD" value="" type="hidden"/>
    <input name="TermUrl" value="https://core.spreedly.com/transaction/SUp3x15f095FvImHf0trucKbxH/redirect" type="hidden"/>
    <input name="Complete" value="Authorize Transaction" type="submit"/>
  </div>
</form>
]]>
  </checkout_form>
  <setup_response>
    <success type="boolean">true</success>
    <message>Checked enrollment status</message>
    <error_code></error_code>
    <checkout_url nil="true"/>
    <created_at type="dateTime">2015-01-08T16:00:02-05:00</created_at>
    <updated_at type="dateTime">2015-01-08T16:00:03-05:00</updated_at>
  </setup_response>
  <redirect_response>
    <success type="boolean">true</success>
    <message>Succeeded</message>
    <avs_code nil="true"/>
    <avs_message nil="true"/>
    <cvv_code nil="true"/>
    <cvv_message nil="true"/>
    <pending type="boolean">false</pending>
    <result_unknown type="boolean">false</result_unknown>
    <error_code nil="true"/>
    <error_detail nil="true"/>
    <cancelled type="boolean">false</cancelled>
    <fraud_review type="boolean">false</fraud_review>
    <created_at type="dateTime">2015-01-08T16:00:03-05:00</created_at>
    <updated_at type="dateTime">2015-01-08T16:00:03-05:00</updated_at>
  </redirect_response>
</transaction>

The succeeded attribute is now true and we consider the transaction complete. From here you would redirect your customer to a “Thank You” page of some kind, and get on with processing their order now that it’s paid for.

For some gateways and payment methods, the transaction may not be succeeded at this stage. In particular, some gateways won’t be succeeded because funds have not actually reached your account yet. The state attribute of the transaction will be processing to indicate that the transaction was accepted, but it might take a few days for the funds to actually reach your account.

For an offsite Paypal purchase, most of the time the transaction will be succeeded when you get to this point in the process. For some customers though, it’ll be in the processing state because their Paypal account lacks a connection to a credit card and it’s only linked to a checking account. In this case the purchase is really an eCheck in Paypal parlance. These can take a few days for the funds to actually get to you.

It’s up to you at this point how you’d like to respond if the transaction is in the processing state. For some businesses, you may want to simply grant the customer access to whatever service they’re buying, knowing that a significant percentage of them will be successful and that you’ll eventually receive the funds. If you’re shipping a very expensive product, you may want to wait until the funds actually hit your account before shipping.

So how will you know when the state of the transaction actually moves from processing? The answer lies in the following section on Callbacks:

Callbacks

In a best case scenario, customers browsers will always come back to your site after a successful payment, but in the real world, there are various reasons you might never receive the redirect back to your site. In those cases it’s important to still hear back about what happened with the transaction, and the callback_url is your means to do so.

The callback url will receive a POST back of all transactions that have changed since the last callback. In most cases, you’ll receive the redirect and a callback, and the order of the two is not guaranteed. Generally you’ll just want to pay attention to the first one, and ignore the second one that comes in since you’ve already handled the transaction.

In some cases though such as a Paypal eCheck, the transaction initially moved to a state of processing and stayed there for a few days before the funds were actually transferred. You’ll eventually receive a callback indicating that the transaction has moved to a different state. Here are the possible states for a transaction:

  • succeeded: The transaction has succeeded and funds have been received.
  • processing: The transaction has been accepted. Funds have not yet been received.
  • pending: The transaction needs further processing which typically involves redirecting the customer to a redirect_url to allow them to specify a payment method.
  • gateway_processing_failed: The transaction failed because the gateway declined the charge for some reason.
  • gateway_processing_result_unknown: We had difficulty communicating with the service and we’re unsure what the result of the operation was. (timeouts, connection errors, etc).
  • failed: The transaction failed. This could be caused by a number of things such as the payment method not being valid, the payment method being redacted, etc.
  • gateway_setup_failed: The transaction failed because the attempt to setup the transaction on the offsite gateway failed.

The callback POST looks like this:


<transactions>
  <transaction>
    <on_test_gateway type="boolean">false</on_test_gateway>
    <created_at type="dateTime">2015-01-08T15:31:32-05:00</created_at>
    <updated_at type="dateTime">2015-01-08T15:31:32-05:00</updated_at>
    <succeeded type="boolean">true</succeeded>
    <state>succeeded</state>
    <token>APIKmsDwoRKre5QNz5UrC02eOen</token>
    <transaction_type>OffsiteAuthorization</transaction_type>
    <order_id nil="true"/>
    <ip nil="true"/>
    <description nil="true"/>
    <email nil="true"/>
    <merchant_name_descriptor nil="true"/>
    <merchant_location_descriptor nil="true"/>
    <gateway_specific_fields nil="true"/>
    <gateway_specific_response_fields nil="true"/>
    <gateway_transaction_id nil="true"/>
    <amount type="integer">1400</amount>
    <currency_code>USD</currency_code>
    <reference>TheReferenceId</reference>
    <message key="messages.transaction_succeeded">Succeeded!</message>
    <gateway_token>8XJtbE1p4NTZ6fFqwwn0GrkjEmW</gateway_token>
    <response>
      <success type="boolean">true</success>
      <message>Succeeded</message>
      <avs_code nil="true"/>
      <avs_message nil="true"/>
      <cvv_code nil="true"/>
      <cvv_message nil="true"/>
      <pending type="boolean">false</pending>
      <result_unknown type="boolean">false</result_unknown>
      <error_code nil="true"/>
      <error_detail nil="true"/>
      <cancelled nil="true"/>
      <fraud_review type="boolean">false</fraud_review>
      <created_at type="dateTime">2015-01-08T15:31:32-05:00</created_at>
      <updated_at type="dateTime">2015-01-08T15:31:32-05:00</updated_at>
    </response>
    <api_urls>
      <callback_conversations>https://core.spreedly.com/v1/callbacks/XjCHPJad2pDbIKKq2fOJezXmxiM/conversations.xml</callback_conversations>
    </api_urls>
    <signed>
      <signature>81c296289369d1331e17fc5a8c82451282fc0889</signature>
      <fields>amount created_at currency_code ip on_test_gateway order_id state succeeded token transaction_type updated_at</fields>
      <algorithm>sha1</algorithm>
    </signed>
    <payment_method>
      <token>UAUT1ArRmsXXuXdzS3XbALyYgNr</token>
      <created_at type="dateTime">2015-01-08T15:31:32-05:00</created_at>
      <updated_at type="dateTime">2015-01-08T15:31:32-05:00</updated_at>
      <email nil="true"/>
      <data>
        <email>gandalf@wizards.com</email>
      </data>
      <storage_state>retained</storage_state>
      <test type="boolean">true</test>
      <payment_method_type>sprel</payment_method_type>
      <errors>
      </errors>
    </payment_method>
    <callback_url>http://127.0.0.1:3044</callback_url>
    <redirect_url>http://example.com/handle_redirect</redirect_url>
    <checkout_url>https://core.spreedly.com/sprel/8XJtbE1p4NTZ6fFqwwn0GrkjEmW/checkout/APIKmsDwoRKre5QNz5UrC02eOen</checkout_url>
    <checkout_form>
      <![CDATA[
<form action="" method="POST">
  <div>
    <input name="PaReq" value="" type="hidden"/>
    <input name="MD" value="" type="hidden"/>
    <input name="TermUrl" value="https://core.spreedly.com/transaction/APIKmsDwoRKre5QNz5UrC02eOen/redirect" type="hidden"/>
    <input name="Complete" value="Authorize Transaction" type="submit"/>
  </div>
</form>
]]>
    </checkout_form>
    <setup_response>
      <success type="boolean">true</success>
      <message nil="true"/>
      <error_code nil="true"/>
      <checkout_url>https://core.spreedly.com/sprel/8XJtbE1p4NTZ6fFqwwn0GrkjEmW/checkout/APIKmsDwoRKre5QNz5UrC02eOen</checkout_url>
      <created_at type="dateTime">2015-01-08T15:31:32-05:00</created_at>
      <updated_at type="dateTime">2015-01-08T15:31:32-05:00</updated_at>
    </setup_response>
    <redirect_response>
      <success type="boolean">true</success>
      <message>Succeeded</message>
      <avs_code nil="true"/>
      <avs_message nil="true"/>
      <cvv_code nil="true"/>
      <cvv_message nil="true"/>
      <pending type="boolean">false</pending>
      <result_unknown type="boolean">false</result_unknown>
      <error_code nil="true"/>
      <error_detail nil="true"/>
      <cancelled nil="true"/>
      <fraud_review type="boolean">false</fraud_review>
      <created_at type="dateTime">2015-01-08T15:31:32-05:00</created_at>
      <updated_at type="dateTime">2015-01-08T15:31:32-05:00</updated_at>
    </redirect_response>
  </transaction>
  <transaction>
    <on_test_gateway type="boolean">false</on_test_gateway>
    <created_at type="dateTime">2015-01-08T15:55:02-05:00</created_at>
    <updated_at type="dateTime">2015-01-08T15:55:02-05:00</updated_at>
    <succeeded type="boolean">true</succeeded>
    <state>succeeded</state>
    <token>2tLQS3ebpsCBYvtNtgc2kXXXkL</token>
    <transaction_type>Purchase</transaction_type>
    <order_id nil="true"/>
    <ip nil="true"/>
    <description nil="true"/>
    <email nil="true"/>
    <merchant_name_descriptor nil="true"/>
    <merchant_location_descriptor nil="true"/>
    <gateway_specific_fields nil="true"/>
    <gateway_specific_response_fields nil="true"/>
    <gateway_transaction_id nil="true"/>
    <amount type="integer">100</amount>
    <currency_code>USD</currency_code>
    <retain_on_success type="boolean">false</retain_on_success>
    <payment_method_added type="boolean">false</payment_method_added>
    <message key="messages.transaction_succeeded">Succeeded!</message>
    <gateway_token>8XJtbE1p4NTZ6fFqwwn0GrkjEmW</gateway_token>
    <response>
      <success type="boolean">true</success>
      <message>Succeeded</message>
      <avs_code nil="true"/>
      <avs_message nil="true"/>
      <cvv_code nil="true"/>
      <cvv_message nil="true"/>
      <pending type="boolean">false</pending>
      <result_unknown type="boolean">false</result_unknown>
      <error_code nil="true"/>
      <error_detail nil="true"/>
      <cancelled type="boolean">false</cancelled>
      <fraud_review type="boolean">false</fraud_review>
      <created_at type="dateTime">2015-01-08T15:55:02-05:00</created_at>
      <updated_at type="dateTime">2015-01-08T15:55:02-05:00</updated_at>
    </response>
    <api_urls>
      <callback_conversations>https://core.spreedly.com/v1/callbacks/XjCHPJad2pDbIKKq2fOJezXmxiM/conversations.xml</callback_conversations>
    </api_urls>
    <signed>
      <signature>4c18641228a9415318d9f89cee64c0f26f075eaa</signature>
      <fields>amount created_at currency_code ip on_test_gateway order_id state succeeded token transaction_type updated_at</fields>
      <algorithm>sha1</algorithm>
    </signed>
    <payment_method>
      <token>IPhWm8DU6sKOODBQnXb087f9yIA</token>
      <created_at type="dateTime">2015-01-08T15:55:02-05:00</created_at>
      <updated_at type="dateTime">2015-01-08T15:55:02-05:00</updated_at>
      <email nil="true"/>
      <data nil="true"/>
      <storage_state>used</storage_state>
      <test type="boolean">true</test>
      <last_four_digits>3886</last_four_digits>
      <first_six_digits>455676</first_six_digits>
      <card_type>visa</card_type>
      <first_name>Bob</first_name>
      <last_name>Smith</last_name>
      <month type="integer">2</month>
      <year type="integer">2020</year>
      <address1 nil="true"/>
      <address2 nil="true"/>
      <city nil="true"/>
      <state nil="true"/>
      <zip nil="true"/>
      <country nil="true"/>
      <phone_number nil="true"/>
      <full_name>Bob Smith</full_name>
      <eligible_for_card_updater type="boolean">true</eligible_for_card_updater>
      <shipping_address1 nil="true"/>
      <shipping_address2 nil="true"/>
      <shipping_city nil="true"/>
      <shipping_state nil="true"/>
      <shipping_zip nil="true"/>
      <shipping_country nil="true"/>
      <shipping_phone_number nil="true"/>
      <payment_method_type>credit_card</payment_method_type>
      <errors>
      </errors>
      <verification_value></verification_value>
      <number>XXXX-XXXX-XXXX-3886</number>
    </payment_method>
    <callback_url>http://127.0.0.1:3044</callback_url>
    <redirect_url>http://example.com/handle_redirect</redirect_url>
    <checkout_url nil="true"/>
    <checkout_form>
      <![CDATA[
<form action="https://core.spreedly.com/test/8XJtbE1p4NTZ6fFqwwn0GrkjEmW/auth/2tLQS3ebpsCBYvtNtgc2kXXXkL" method="POST">
  <div>
    <input name="PaReq" value="" type="hidden"/>
    <input name="MD" value="" type="hidden"/>
    <input name="TermUrl" value="https://core.spreedly.com/transaction/2tLQS3ebpsCBYvtNtgc2kXXXkL/redirect" type="hidden"/>
    <input name="Complete" value="Authorize Transaction" type="submit"/>
  </div>
</form>
]]>
    </checkout_form>
    <setup_response>
      <success type="boolean">true</success>
      <message>Checked enrollment status</message>
      <error_code></error_code>
      <checkout_url nil="true"/>
      <created_at type="dateTime">2015-01-08T15:55:02-05:00</created_at>
      <updated_at type="dateTime">2015-01-08T15:55:02-05:00</updated_at>
    </setup_response>
    <redirect_response>
      <success type="boolean">true</success>
      <message>Succeeded</message>
      <avs_code nil="true"/>
      <avs_message nil="true"/>
      <cvv_code nil="true"/>
      <cvv_message nil="true"/>
      <pending type="boolean">false</pending>
      <result_unknown type="boolean">false</result_unknown>
      <error_code nil="true"/>
      <error_detail nil="true"/>
      <cancelled type="boolean">false</cancelled>
      <fraud_review type="boolean">false</fraud_review>
      <created_at type="dateTime">2015-01-08T15:55:02-05:00</created_at>
      <updated_at type="dateTime">2015-01-08T15:55:02-05:00</updated_at>
    </redirect_response>
  </transaction>
</transactions>

Note that the callback response is signed; this allows you to process the results of the callback without having to round trip back out to Spreedly (though you certainly can round trip if you’d like - see below). Since an attacker could call your callback url with a valid looking transaction, the signature allows you to verify that the information is really coming from Spreedly. Full details on validating the signature is in our Signing Reference.

We recognize that some customers may not be interested in going through the trouble of writing code to validate the signature of the callback response. In this case, you could simply grab the tokens of the transactions you receive in the callback and then make an authenticated API call to retrieve the details each transaction. Because that call is authenticated and you’re making the request, there’s no need to verify where the information is coming from.

Callback Response

You should respond with a 200 OK response within 5 seconds. If Core does not receive back a 200 response within this time, it will retry the callback again at least 4 times at ever-increasing intervals.

If you need to do potentially time-consuming operations when a callback is received, we recommend doing them asynchronously to avoid being timed out.

Error handling

Of course, things don’t go exactly right all the time. And with offsite transactions, there are more places that things can break, since there are more interactions with the gateway. The state element will change to reflect failures when processing:


curl https://core.spreedly.com/v1/transactions/TC7i2O0mhzpes1BaCaIdzGzHQuL.xml \
  -u 'Ll6fAtoVSTyVMlJEmtpoJV8S:RKOCG5D8D3fZxDSg504D0IxU2XD4Io5VXmyzdCtTivHFTTSy'

<transaction>
  <on_test_gateway type="boolean">false</on_test_gateway>
  <created_at type="dateTime">2015-01-08T16:03:43-05:00</created_at>
  <updated_at type="dateTime">2015-01-08T16:03:49-05:00</updated_at>
  <succeeded type="boolean">true</succeeded>
  <state>succeeded</state>
  <token>SrkEQGNZSmjJYQTEepl5de7Wc7M</token>
  <transaction_type>Purchase</transaction_type>
  <order_id nil="true"/>
  <ip nil="true"/>
  <description nil="true"/>
  <email nil="true"/>
  <merchant_name_descriptor nil="true"/>
  <merchant_location_descriptor nil="true"/>
  <gateway_specific_fields nil="true"/>
  <gateway_specific_response_fields nil="true"/>
  <gateway_transaction_id nil="true"/>
  <amount type="integer">100</amount>
  <currency_code>USD</currency_code>
  <retain_on_success type="boolean">false</retain_on_success>
  <payment_method_added type="boolean">false</payment_method_added>
  <message key="messages.transaction_succeeded">Succeeded!</message>
  <gateway_token>8XJtbE1p4NTZ6fFqwwn0GrkjEmW</gateway_token>
  <response>
    <success type="boolean">true</success>
    <message>Succeeded</message>
    <avs_code nil="true"/>
    <avs_message nil="true"/>
    <cvv_code nil="true"/>
    <cvv_message nil="true"/>
    <pending type="boolean">false</pending>
    <result_unknown type="boolean">false</result_unknown>
    <error_code nil="true"/>
    <error_detail nil="true"/>
    <cancelled type="boolean">false</cancelled>
    <fraud_review type="boolean">false</fraud_review>
    <created_at type="dateTime">2015-01-08T16:03:43-05:00</created_at>
    <updated_at type="dateTime">2015-01-08T16:03:49-05:00</updated_at>
  </response>
  <api_urls>
    <callback_conversations>https://core.spreedly.com/v1/callbacks/XjCHPJad2pDbIKKq2fOJezXmxiM/conversations.xml</callback_conversations>
  </api_urls>
  <payment_method>
    <token>OyJJXGWSaManB7NhahLTsceOFi</token>
    <created_at type="dateTime">2015-01-08T16:03:42-05:00</created_at>
    <updated_at type="dateTime">2015-01-08T16:03:43-05:00</updated_at>
    <email nil="true"/>
    <data nil="true"/>
    <storage_state>used</storage_state>
    <test type="boolean">true</test>
    <last_four_digits>3886</last_four_digits>
    <first_six_digits>455676</first_six_digits>
    <card_type>visa</card_type>
    <first_name>Bob</first_name>
    <last_name>Smith</last_name>
    <month type="integer">2</month>
    <year type="integer">2020</year>
    <address1 nil="true"/>
    <address2 nil="true"/>
    <city nil="true"/>
    <state nil="true"/>
    <zip nil="true"/>
    <country nil="true"/>
    <phone_number nil="true"/>
    <full_name>Bob Smith</full_name>
    <eligible_for_card_updater type="boolean">true</eligible_for_card_updater>
    <shipping_address1 nil="true"/>
    <shipping_address2 nil="true"/>
    <shipping_city nil="true"/>
    <shipping_state nil="true"/>
    <shipping_zip nil="true"/>
    <shipping_country nil="true"/>
    <shipping_phone_number nil="true"/>
    <payment_method_type>credit_card</payment_method_type>
    <errors>
    </errors>
    <verification_value></verification_value>
    <number>XXXX-XXXX-XXXX-3886</number>
  </payment_method>
  <callback_url>http://127.0.0.1:3044</callback_url>
  <redirect_url>http://example.com/handle_redirect</redirect_url>
  <checkout_url nil="true"/>
  <checkout_form>
    <![CDATA[
<form action="https://core.spreedly.com/test/8XJtbE1p4NTZ6fFqwwn0GrkjEmW/auth/SrkEQGNZSmjJYQTEepl5de7Wc7M" method="POST">
  <div>
    <input name="PaReq" value="" type="hidden"/>
    <input name="MD" value="" type="hidden"/>
    <input name="TermUrl" value="https://core.spreedly.com/transaction/SrkEQGNZSmjJYQTEepl5de7Wc7M/redirect" type="hidden"/>
    <input name="Complete" value="Authorize Transaction" type="submit"/>
  </div>
</form>
]]>
  </checkout_form>
  <setup_response>
    <success type="boolean">true</success>
    <message>Checked enrollment status</message>
    <error_code></error_code>
    <checkout_url nil="true"/>
    <created_at type="dateTime">2015-01-08T16:03:43-05:00</created_at>
    <updated_at type="dateTime">2015-01-08T16:03:49-05:00</updated_at>
  </setup_response>
  <redirect_response>
    <success type="boolean">true</success>
    <message>Succeeded</message>
    <avs_code nil="true"/>
    <avs_message nil="true"/>
    <cvv_code nil="true"/>
    <cvv_message nil="true"/>
    <pending type="boolean">false</pending>
    <result_unknown type="boolean">false</result_unknown>
    <error_code nil="true"/>
    <error_detail nil="true"/>
    <cancelled type="boolean">false</cancelled>
    <fraud_review type="boolean">false</fraud_review>
    <created_at type="dateTime">2015-01-08T16:03:43-05:00</created_at>
    <updated_at type="dateTime">2015-01-08T16:03:49-05:00</updated_at>
  </redirect_response>
</transaction>

In general you can just depend on the message element when something goes wrong - we do our best to provide a useful message there that you can display rather than having to dig around in the transaction details. That said, up to three responses are returned with the transaction: setup_response, redirect_response, and callback_response. These may have additional error details that will aid in debugging any issues.

The transaction’s state can also be an indication as to what has failed. For example, when initiating the purchase, we setup a transaction on the offsite gateway. If that setup process fails, the state of the transaction will be gateway_setup_failed. (You can simulate this with the Test gateway by specifying an amount of 44 cents).

Like always, you can use the transcript call to get a verbatim copy of Core’s actual conversation with the gateway.

Callback Debugging

Core not calling your callback_url? In the transaction XML, you’ll notice an api_urls element that has a conversations url - if you call that url via the API, you’ll be able to see a history of all of our attempts to contact your callback url, similar to the transcript we save of conversations with the gateway.