Lifecycle Management

Spreedly’s Lifecycle Management feature allows you to always keep your customers’ card details up-to-date. When your customers’ credit card numbers expire or are updated, Lifecycle Management protects you from the lost revenue, involuntary churn, and decreased customer satisfaction associated with outdated payment information. No additional development is required on your side since cards are updated behind the scenes.

📘

Lifecycle Management is a feature under Advanced Vault. To access Account Updater, the organization, environments and payment methods that you are wanting to update through the service must also be enrolled in the Advanced Vault Product.

How Lifecycle Management Works

When you opt-in, Spreedly defaults to submitting all retained, non-test, Visa, MasterCard, and Discover cards in your Spreedly vault for automatic updating twice monthly in batch on the 1st and 15th of every month. Additionally, Spreedly supports real time updates for Mastercard Cards. Spreedly will subscribe cards with the service once they are made eligible, and process updates as they are received. The enrollment process for either service and the appearance of the updates are exactly the same, with the only distinction being the time periods when these updates are recieved.

The table below lists current regional participation by card brand:

SUPPORTED SCHEMESMARKET COVERAGEModel
VisaNorth America: Near full participation
Europe: Near full participation in UK, Ireland, Italy, Greece. High participation elsewhere, excluding Hungary and Turkey.
Central Europe, Middle East, Africa: High participation.
Batch
MastercardNorth America: near full participation.
Europe: Near full participation in UK, Ireland, and Italy. High participation elsewhere.
Latin America: High participation
Asia Pacific: High participation, excluding India.
Central Europe, Middle East, Africa: High participation.
Batch and
Real Time
DiscoverNorth America: Near full participation.Batch

Lifecycle Management control levels

Organizational-level controls

Opting in to Lifecycle Management happens at the organization level - all environments will be affected. By enabling the Lifecycle Management feature, all eligible cards will be sent for update, but you will only be charged for those cards that are identified to be updated. Turning Lifecycle Management off at the organizational level will not affect an individual card’s eligibility status.

Environment-level controls

For more granular control, you can opt for specific environments to have their cards sent to Lifecycle Management while excluding other environments from card updates. In order to enable Lifecycle Management at the environment level, it must first be enabled for an organization.

Managing Environments for Lifecycle Management

Given Lifecycle Management is enabled for the organization, toggle “Allow Account Updater at Environment Level” in the Organization Settings. This setting can be toggled in the Spreedly App once you are logged in. Once this is enabled, only environments specifically toggled in the UI or are marked as au_enabled will be sent to Account Updater.

You may now enable individual environments via the UI under each environment by toggling the setting “Environment Level Account Updater Enabled” (the property on the Environment model is au_enabled). Alternatively, environments can be enabled via the environment create and update endpoints by passing the field au_enabled: true in the body.

📘

This setting is disabled by default for all environments. Thus, if only “Allow Account Updater at Environment Level” is enabled in the Organization Settings, none of the environments will participate in the Lifecycle Management process until they are specifically enabled.

Card-Level Controls

You can optionally exclude individual cards from the Lifecycle Management service via our API. For example, you may want to exclude a card if your customer has paused their subscription. Payment methods include an eligible_for_card_updater field that when set to false will result in Spreedly excluding the associated card from subsequent submissions to the Lifecycle Managementr service. You can set this field to true to resume submitting the given card at any time.

Note: Payment methods that have the eligible_for_card_updater field set to false will continue to be excluded from being updated by Account Updater regardless of the Environment level Lifecycle Management enablement settings.

Configuration Matrix

ORG AUALLOW AU AT ENVIRONMENT LEVELAU_ENABLED (ENVIRONMENT)ELIGIBLE_FOR_CARD_UPDATER (PAYMENT METHOD)EXPECTED RESULTS
OffN/AN/ATrueCard is eligible but not sent to AU because AU is Off for the Organization
OnOffN/ATrueAll cards in the organization with eligible_for_card_updater: true are sent to AU
OnOnFalseTrueAU is On at the environment level but the specific environment is not au_enabled. No cards are sent from that environment regardless of eligibility
OnOnTrueTrueAll environments with au_enabled: true or “Environment Level Account Updater Enabled” toggled on will have all eligible cards sent to AU
OnOn/OffTrue/FalseFalseCards with eligible_for_card_updater: false will not be sent to AU

Analyze Results

Dashboard

Results of Lifecycle Management submissions can be tracked in your Spreedly dashboard, where you can view summary statistics by month for a selected time period, see detailed per-card results and download a CSV report for a given period right from the dashboard. The status for each card is shown:

ReplacePaymentMethod(billable) - The number and/or expiration date has been updated.
InvalidReplacePaymentMethod(not billed) - We received either a new card number or expiration date that was invalid. The payment method was not updated, and there is no charge.
ContactCardHolder(billable) - The card network identified this account needs to be updated but the issuer did not provide the updated values. Contact the cardholder for a new number and/or expiration date. Spreedly will automatically unenroll the card from LCM after 2 sequential ContactCardHolder responses.
ClosePaymentMethod(billable) - The account is no longer open and should no longer be used. The payment method will remain available, but Spreedly’s Lifecycle Management will no longer attempt to update it.

Custom Analytics

Spreedly also provides Custom Analytics that allows the customer to build their own dashboard and visualizations that best suits their needs. For more details on implementing, please see the Custom Analytics for Dashboard

Callbacks

As an alternative to viewing results in the Spreedly dashboard, callbacks are provided that will push Lifecycle Management results to a destination of your choosing. These notifications are delivered via an HTTPS POST to a public-facing URL on your server. 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.

Since Lifecycle Management operates in a batch manner and card updates may result in a large number of transactions, Spreedly only delivers callbacks every few minutes. A single callback may contain up to 150 transactions with a maximum delivery of approximately 500 callbacks within a 24 hour period period per environment when using an environment based callback URL or per URL when using an override.

The incoming callback POST requests will be structured as follows:

{  
  "transactions": \[  
    {  
      "token": "S6cl5f11LqdVLe9m1TtQGMSMUuz",  
      "created_at": "2019-02-13T13:40:43Z",  
      "updated_at": "2019-02-13T13:42:48Z",  
      "succeeded": true,  
      "transaction_type": "ReplacePaymentMethod",  
      "state": "succeeded",  
      "account_key": "2H2HmVvjh171NvjCzr37D7jWG6c",  
      "environment_key": "DDB58wpfgxHA1qD7p0LkqCLBnZw",  
      "message_key": "messages.transaction_succeeded",  
      "message": "Succeeded!",  
      "payment_method": {  
        "token": "EjpBzUVNHWnF1Y0MNaefuNpXwyR",  
        "created_at": "2019-02-13T13:27:53Z",  
        "updated_at": "2019-02-13T13:40:43Z",  
        "email": "[[email protected]](mailto:[email protected])",  
        "data": null,  
        "storage_state": "retained",  
        "test": false,  
        "metadata": null,  
        "callback_url": "<https://7fba2acd.ngrok.io">,  
        "last_four_digits": "0003",  
        "first_six_digits": "511201",  
        "card_type": "master",  
        "first_name": "Mighty",  
        "last_name": "Mouse",  
        "month": 1,  
        "year": 2050,  
        "address1": null,  
        "address2": null,  
        "city": null,  
        "state": null,  
        "zip": null,  
        "country": null,  
        "phone_number": null,  
        "company": null,  
        "full_name": "Mighty Mouse",  
        "eligible_for_card_updater": true,  
        "shipping_address1": null,  
        "shipping_address2": null,  
        "shipping_city": null,  
        "shipping_state": null,  
        "shipping_zip": null,  
        "shipping_country": null,  
        "shipping_phone_number": null,  
        "payment_method_type": "credit_card",  
        "errors": \[],  
        "fingerprint": "84fd610e28258201fffea68dc0f9e7c66859",  
        "verification_value": "",  
        "number": "XXXX-XXXX-XXXX-0003"  
      },  
      "signed": {  
        "signature": "03268a0a7ccb7a1e18cd99f60513ec9814f9d240",  
        "fields": "token created_at updated_at succeeded transaction_type state",  
        "algorithm": "sha1"  
      }  
    },  
    {  
      "token": "I5kuefCHe8Tvm9HypLRLIKYHCmm",  
      "created_at": "2019-02-13T13:40:40Z",  
      "updated_at": "2019-02-13T13:42:48Z",  
      "succeeded": true,  
      "transaction_type": "ClosePaymentMethod",  
      "state": "succeeded",  
      "account_key": "2H2HmVvjh171NvjCzr37D7jWG6c",  
      "environment_key": "DDB58wpfgxHA1qD7p0LkqCLBnZw",  
      "message_key": "messages.transaction_succeeded",  
      "message": "Succeeded!",  
      "payment_method": {  
        "token": "7xHGM9h7a8OoUboXPNHvY5pihgv",  
        "created_at": "2019-02-13T13:27:56Z",  
        "updated_at": "2019-02-13T13:40:40Z",  
        "email": "[[email protected]](mailto:[email protected])",  
        "data": null,  
        "storage_state": "retained",  
        "test": false,  
        "metadata": null,  
        "callback_url": "<https://7fba2acd.ngrok.io">,  
        "last_four_digits": "0002",  
        "first_six_digits": "601101",  
        "card_type": "discover",  
        "first_name": "Mighty",  
        "last_name": "Mouse",  
        "month": 8,  
        "year": 1950,  
        "address1": null,  
        "address2": null,  
        "city": null,  
        "state": null,  
        "zip": null,  
        "country": null,  
        "phone_number": null,  
        "company": null,  
        "full_name": "Mighty Mouse",  
        "eligible_for_card_updater": false,  
        "shipping_address1": null,  
        "shipping_address2": null,  
        "shipping_city": null,  
        "shipping_state": null,  
        "shipping_zip": null,  
        "shipping_country": null,  
        "shipping_phone_number": null,  
        "payment_method_type": "credit_card",  
        "errors": \[],  
        "fingerprint": "2b4184e42067002e39fb3f5a7e9275f34b17",  
        "verification_value": "",  
        "number": "XXXX-XXXX-XXXX-0002"  
      },  
      "signed": {  
        "signature": "808eada381675ff34c3d0b67b010dda965445451",  
        "fields": "token created_at updated_at succeeded transaction_type state",  
        "algorithm": "sha1"  
      }  
    }  
  ]  
}

Note that each transaction contained in a callback is signed; this allows you to process the results of the callback without having to round trip back 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 in each transaction is really coming from Spreedly. In the example callback request shown above, the signatures were created using the following signing secret:

4ziASKWGGV1zdrUbSN6vq2CjjDPw2hzJSvsGLhxces1aORBKKsRyJwb8DfGQ6J3q

Here is an example of ruby code you could use to verify the signatures in the above callback request (with most of the fields omitted for brevity):

require 'openssl'

def signature_for(secret, transaction)  
  algorithm = transaction['signed']['algorithm']

  fields = transaction['signed']['fields'].split(' ')

  values = fields.collect { |field| transaction[field] }

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

document = JSON.parse(  
  %(  
    {  
      "transactions": [  
        {  
          "token": "S6cl5f11LqdVLe9m1TtQGMSMUuz",  
          "created_at": "2019-02-13T13:40:43Z",  
          "updated_at": "2019-02-13T13:42:48Z",  
          "succeeded": true,  
          "transaction_type": "ReplacePaymentMethod",  
          "state": "succeeded",  
          "signed": {  
            "signature": "03268a0a7ccb7a1e18cd99f60513ec9814f9d240",  
            "fields": "token created_at updated_at succeeded transaction_type state",  
            "algorithm": "sha1"  
          }  
        },  
        {  
          "token": "I5kuefCHe8Tvm9HypLRLIKYHCmm",  
          "created_at": "2019-02-13T13:40:40Z",  
          "updated_at": "2019-02-13T13:42:48Z",  
          "succeeded": true,  
          "transaction_type": "ClosePaymentMethod",  
          "state": "succeeded",  
          "signed": {  
            "signature": "808eada381675ff34c3d0b67b010dda965445451",  
            "fields": "token created_at updated_at succeeded transaction_type state",  
            "algorithm": "sha1"  
          }  
        }  
      ]  
    }  
  )  
)

document['transactions'].each do |transaction|  
  puts signature_for(  
    "4ziASKWGGV1zdrUbSN6vq2CjjDPw2hzJSvsGLhxces1aORBKKsRyJwb8DfGQ6J3q",  
    transaction  
  )  
end
"03268a0a7ccb7a1e18cd99f60513ec9814f9d240"  
"808eada381675ff34c3d0b67b010dda965445451"

You can find more details about signed requests in our signing reference.

We recognize that some customers are not interested in going through the trouble of writing code to validate the signature of the callback response. In this case, you can use the tokens of the transactions you receive in the callback and then make an authenticated API call to retrieve the details of 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 back with a 200 OK response within 5 seconds. If Spreedly 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. Additionally, callbacks should be treated as idempotent since they may be sent more than once.

If you are not receiving callbacks, ensure that the callback URL provided utilizes HTTPS with standard port 443.

Getting started with callbacks

To get started with callbacks on Lifecycle Management, a callback URL must be added to your Spreedly environment. Log in, select the environment you wish configure, and add a callback URL. If you store payment methods in multiple environments, a callback URL must be configured for each environment.

Additionally, Spreedly provides the ability to override the per-environment level callback URL with a payment method specific callback URL. This override is useful in situations where card update notifications are to be delivered to a location different from the environment level callback URL. To override the environment level callback URL, both the create payment method and update payment method API calls include a callback_url property that can be set. Once set, any update notifications related to this payment method will be delivered to the callback_url set on the payment method instead of the environment. By default, the callback_url property on a payment method is null and will only take precedence over the environment level setting when set. Spreedly does not update all of your payment methods when the environment level setting is present.

Fields

The following fields on a payment method can be changed by the Account Updater service with data directly from the card schemes:

number: The obscured new credit card number returned by Account Updater
month: The new expiration month returned by Account Updater
year: The new expiration year returned by Account Updater

The following fields on a payment method can be changed by Spreedly based on data from the card schemes:

fingerprint: The fingerprint is a randomly generated identifer to cards that share the same number (PAN). If the PAN changed, Spreedly will generate a new identifier for the number. See our fingerprinting guide for more information.
card_type: Spreedly will detect and update the card_type field if the PAN changed. Note: if a payment method’s card_type changes, any stored credentials stored on that payment method are considered invalid and deleted. See our stored credentials guide for more information.
last_four_digits: Spreedly will update the last_four_digits of the credit card number if the PAN changed.
first_six_digits: Spreedly will update the first_six_digits of the credit card number if the PAN changed.

The following field can be changed by Spreedly's Advanced Vault Automations as a result of updates received

eligible_for_account_updater:After a single ClosePaymentMethod response or two successive ContactCardHolder responses, the value in this field will be changed from true to false.

Email Notifications

We will send two e-mail notifications when your cards are updated. Notifications will be sent to all active users associated with the organization. The first email is notice that the card update process has started. Example:

The second email is notice that card updates have completed and contains aggregate information about the updates that were performed. Example:

Please visit the Help Center for a detailed walkthrough on pricing, opting-in, and opting-out.