Batch Export Payment Method Distribution

If you need to export more than one card at a time to an SFTP endpoint, use batch export PMD. If you need to send a single payment method to an HTTP endpoint, use the single card form of PMD instead.

Provisioning a test receiver

Spreedly provides a test receiver that simulates the functionality of a production receiver, but does not deliver data to a third-party endpoint. The test receiver will ‘echo’ the parsed and formatted request in the delivery response, with sensitive data scrubbed. This allows you to validate the deliver API request while ensuring the request is properly formatted using our provided receiver variables and functions. Additionally, the test receiver can only distribute test payment methods.

Begin your integration by creating a test receiver pointing to the endpoint host you wish to distribute to (the sftp:// and domain name, but not the path).

$ curl https://core.spreedly.com/v1/receivers.json \
  -u 'C7cRfNJGODKh4Iu5Ox3PToKjniY:4UIuWybmdythfNGPqAqyQnYha6s451ri0fYAo4p3drZUi7q2Jf4b7HKg8etDtoKJ' \
  -H 'Content-Type: application/json' \
  -d '{"receiver":
        {
          "receiver_type": "test",
          "hostnames": "sftp://testserver.com",
          "protocol": {"user": "test_user", "password": "test_password"}
        }
      }'

The SFTP protocol requires a username and password. When provisioning your receiver, specify the user and password in the protocol element.

The response will include a token value identifying the new receiver, which you will use when you need to distribute card data from Spreedly to the receiver.

{
  "receiver": {
    "company_name": "TEST",
    "receiver_type": "test",
    "token": "Rz3rhs0J7V2UW41egNYTBJiMpUQ",
    "hostnames": "sftp://testserver.com",
    "state": "retained",
    "created_at": "2018-04-20T12:58:03Z",
    "updated_at": "2018-04-20T12:58:03Z",
    "credentials": null,
    "protocol": {
      "user": "test_user"
    }
  }
}

Export payment methods

Once you’ve specified where to export card data (the receiver), you have to specify what to export. The best way to think about batch export PMD is that you define the raw file that will get sent to the SFTP endpoint and all Spreedly does is populate the file with the sensitive data from your credit card vault before sending it. Once you have a receiver provisioned, you need to tell Spreedly how to build the file including the sensitive card data. This is done using a simple templating language and the export API endpoint.

There are four basic properties that define the export of payment methods: The tokens of the payment methods to be exported, the SFTP URL to send the file to, the callback URL to receive the final transaction state, and the file format itself. The file size limit is 1MB. A simple PMD batch export request might look like the following:

curl https://core.spreedly.com/v1/receivers/UKjXqMKs10ChhaB83j6uLjUbb2U/export.json \
  -u 'C7cRfNJGODKh4Iu5Ox3PToKjniY:4UIuWybmdythfNGPqAqyQnYha6s451ri0fYAo4p3drZUi7q2Jf4b7HKg8etDtoKJ' \
  -H 'Content-Type: application/json' \
  -d '{
        "export": {
          "payment_method_tokens": ["56wyNnSmuA6CWYP7w0MiYCVIbW6", "TtSvidJm3e4Lmza4l8Je5Bwuqmf"],
          "url": "sftp://testserver.com/path/to/filename.txt",
          "callback_url":"https://yoursite.com/spreedly/tx_callback",
          "body": "{ \"cards\": [{{#payment_methods}}\"{{credit_card_number}}\",{{/payment_methods}}] }"
        }
      }'

Template variables

When you invoke the export endpoint on the Spreedly API, you have to tell Spreedly how to format the file that will be sent to the SFTP receiver. The url and body values all accept templates using template syntax that is evaluated at export time - allowing you to build the required file format while not directly accessing sensitive information.

For instance, if you need to create the following CSV file to export:

identifier,number,cvv
1234abcd5678efgh,4111111111111111,123
4321dcba8765hgfe,5555555555554444,321

Then you would specify the following for the export body:

identifier,number,cvv
{{#payment_methods}}{{ credit_card_token }},{{ credit_card_number }},{{ credit_card_verification_value }}
{{/payment_methods}}

The {{#payment_methods}}{{/payment_methods}} syntax iterates over all payment methods specified in the export, and any variables specified using the {{ variable_name }} notation will output the value of that property for the currently exposed payment method.

A list of all available variables can be found in the reference documentation here.

Template functions

There are also a set of receiver functions available to dynamically modify values at export time. For instance, to generate a date to a specific format you can use format_date:

{{#format_date}}%m%y,{{credit_card_expiration_date}}{{/format_date}}

Which will print out the card expiration date in the following format for each card it’s applied to:

0420

Receiver functions all wrap some content, which is then operated on, and requires leading and trailing tags in the syntax {{#function}} and {{/function}}, respectively.

A list of all receiver functions can be found here in the reference docs.

Callback

Unlike the single-card mode of PMD, exporting a batch of cards is an asynchronous operation. This means that when you submit a list of cards to export, Spreedly enqueues the export and returns an API response immediately reflecting the fact that the resulting transaction is pending:

{
  "transaction": {
    "token": "GZ9ylkvYKQKp440LLgTxhSORpx0",
    "transaction_type": "ExportPaymentMethods",
    "state": "pending",
    "created_at": "2018-04-20T14:04:55Z",
    "updated_at": "2018-04-20T14:04:55Z",
    "succeeded": false,
    "message": "Pending",
    "payment_methods_submitted": [
      "1rpKvP8zOUhj4Y9EDrIoIYQzzD5",
      "PUijKEvAOllfOqTAu2ZDeSaAfjc"
    ],
    "payment_method_data": null,
    "payment_methods_included": null,
    "encode_response": null,
    "callback_url": "https://yoursite.com/spreedly/tx_callback",
    "url": "sftp://testserver.com/path/to/filename.txt",
    "payment_methods_excluded": null,
    "response": {
      "status": null
    },
    "receiver": {
      "company_name": "TEST",
      "receiver_type": "test",
      "token": "MZjXWBFfixaYncOUJZKdpj8KnG4",
      "hostnames": "sftp://testserver.com",
      "state": "retained",
      "created_at": "2018-04-20T14:04:55Z",
      "updated_at": "2018-04-20T14:04:55Z",
      "credentials": null,
      "protocol": {
        "user": "user"
      }
    }
  }
}

When you receive this response from the Spreedly API it does not mean the resulting file has been exported. Instead, when the export has been processed, Spreedly will send the resulting transaction record back to you via the URL you specified in the callback_url field of the original export request. This should be a URL to your system that is able to receive a POST request with a list of transactions whose state has been updated. Note that the callback URL must utilize HTTPS and a default port of 443.

The request body to your callback endpoint will look like this:

{
  "transactions": [
    {
      "token": "OZCXojxhJki4Ch8CINYg2Iw58An",
      "transaction_type": "ExportPaymentMethods",
      "state": "completed",
      "created_at": "2016-10-07T15:01:18Z",
      "updated_at": "2016-10-07T15:01:19Z",
      "succeeded": true,
      "message": "Succeeded",
      "payment_methods_submitted": [
        "Q5zPG5NbwmUujR8IOrte9ds6BlK",
        "ELKQxIL9lCdjjfujUzYXTtnqEp8",
        "badCardToken"
      ],
      "payment_method_data": null,
      "payment_methods_included": [
        "Q5zPG5NbwmUujR8IOrte9ds6BlK",
        "ELKQxIL9lCdjjfujUzYXTtnqEp8"
      ],
      "encode_response": null,
      "callback_url": "https://example.com",
      "url": "sftp://testserver.com/path/to/filename.txt",
      "payment_methods_excluded": [
        {
          "badCardToken": "Unable to find the specified payment method."
        }
      ],
      "receiver": {
        "receiver_type": "test",
        "token": "OJDBOWuRDIZ2GlXKpxBxfU9AwIV",
        "hostnames": "sftp://testserver.com",
        "state": "retained",
        "created_at": "2016-10-07T15:01:18Z",
        "updated_at": "2016-10-07T15:01:18Z",
        "credentials": null,
        "protocol": {
          "user": "user"
        }
      }
    }
  ]
}

When you receive a transaction callback, you can use the succeeded field to determine the final status of the export as well as the payment_methods_excluded field to see if any payment methods weren’t exported (and why):

Provisioning a production receiver

Once you have completed testing with a test receiver, you will need to provision a production receiver. Production receiver types are more restricted in that they have a pre-specified hostname that must be added by Spreedly before being used. You will need to contact Spreedly directly to get your receiver endpoint and new receiver_type provisioned.

Once your receiver has been validated by Spreedly, you can provision it using the receiver type given to you by Spreedly (or use one of our pre-existing ones below). You’ll notice production receivers don’t accept hostnames since those values are hard-coded into the receiver:

$ curl https://core.spreedly.com/v1/receivers.json \
  -u 'C7cRfNJGODKh4Iu5Ox3PToKjniY:4UIuWybmdythfNGPqAqyQnYha6s451ri0fYAo4p3drZUi7q2Jf4b7HKg8etDtoKJ' \
  -H 'Content-Type: application/json' \
  -d '{
        "receiver": {
          "receiver_type": "sabre"
        }
      }'


{
  "receiver": {
    "company_name": "Sabre",
    "receiver_type": "sabre",
    "token": "7VdCtEYxttQ7bGMz0EjYZFRFMpH",
    "hostnames": "https://webservices.sabre.com, https://webservices3.sabre.com, https://webservices.havail.sabre.com",
    "state": "retained",
    "created_at": "2017-07-27T17:55:23Z",
    "updated_at": "2017-07-27T17:55:23Z",
    "credentials": null
  }
}

If you have a working PMD integration with a test receiver, flipping to production should be as easy as replacing the receiver token in your existing export call with the new production receiver token.

You can see a list of currently supported receiver types and their allowed API endpoints.

CVVs

According to the PCI standard, CVVs can’t be persisted in long-term storage, even by a vaulting provider like Spreedly. If you need to include CVV data in your receiver call you will need to recache it first before invoking the export receiver call. On successful completion of the receiver call the CVV value will then be wiped from Spreedly, so you will need to recache this data immediately prior to every receiver call.

Errors

If you attempt to add a receiver using a receiver_type that is not available, you will receiver a 403 error response:

$ curl https://core.spreedly.com/v1/receivers.json \
  -u 'C7cRfNJGODKh4Iu5Ox3PToKjniY:4UIuWybmdythfNGPqAqyQnYha6s451ri0fYAo4p3drZUi7q2Jf4b7HKg8etDtoKJ' \
  -H 'Content-Type: application/json' \
  -d '{
        "receiver": {
          "receiver_type": "NOTREAL"
        }
      }'


{
  "errors": [
    {
      "key": "errors.unknown_receiver_type",
      "message": "The specified receiver_type is not supported"
    }
  ]
}

Additionally, you can only specify hostnames for the test receiver type. Production receivers have their hostnames provided by Spreedly to ensure known and PCI compliant export targets. If you attempt to specify your own hostname for a production receiver you will receive an error.

$ curl https://core.spreedly.com/v1/receivers.json \
  -u 'C7cRfNJGODKh4Iu5Ox3PToKjniY:4UIuWybmdythfNGPqAqyQnYha6s451ri0fYAo4p3drZUi7q2Jf4b7HKg8etDtoKJ' \
  -H 'Content-Type: application/json' \
  -d '{
        "receiver": {
          "receiver_type": "sabre",
          "hostnames": "https://maninthemiddle.com"
        }
      }'


{
  "errors": [
    {
      "key": "errors.hostnames_not_allowed",
      "message": "You can't assign a custom hostname to a production receiver. Please don't specify a 'hostnames' property when adding a production receiver."
    }
  ]
}

Next

This guide shows you the steps required to setup a batch export PMD integration, but leaves many details out since every integration has unique requirements. To flesh out your receiver integration, the following may be useful: