Status Visibility
Overview
When your customers go through the Affirm checkout flow, you can see their status in real-time via the Affirm API. We send a webhook request to your server when a customer:
- Enters checkout
- Receives a credit decision
- Confirms their loan
Usage
You can use this information for customer service purposes, such as phone support, or to identify potential customers for email marketing campaigns. You can also use Status Visibility for in-checkout session (push) notifications and feedback. This guide covers how to receive Status Visibility updates from Affirm.
Checkout events
User experience | Affirm event | Webhook event |
---|---|---|
Selects Affirm as payment option and is sent to Affirm | Checkout created | opened |
Creates or signs into an Affirm account | none | none |
Processing Screen | Approval decision | approved not_approved * more_information_needed |
Selects loan terms and agrees to disclosures | none | none |
Clicks confirm and is sent back to merchant site | Checkout confirmation | confirmed |
Event definitions
Customers must opt-in for the approval decisions to be included. The confirmed
event is the only webhook event that is guaranteed to be present for each successful Affirm checkout attempt. All other events (approved/not approved/etc.) will only be present if the customer has opted into the Affirm data sharing policy. Customers are actively prompted to enable data sharing, and they can toggle this setting ("Personalized Services") in their Affirm user profile at any time (https://www.affirm.com/u/#/settings).
Opened
When the customer has opened the checkout link and sees the account sign-in/sign-up screen. This is the first step of the Affirm checkout flow.
Approved
The customer has been approved for their Affirm purchase.
Not Approved
The customer has been declined credit for some reason. The reason is purposely ambiguous and may be due to credit-worthiness, inability to verify identity, or insufficient financial records.
More Information Needed
The customer was required to provide additional information to determine credit-worthiness or identity and exited the checkout without providing the requested information.
Confirmed
The customer has confirmed the loan. This is the last step of the Affirm checkout flow, which should subsequently trigger charge authorization.
Integration
In order to receive Status Visibility updates from Affirm:
Affirm will send an HTTP POST
request to a URL that you provide to your Client Success Manager at Affirm. That URL should be a page or endpoint that you've set up to receive requests from our servers.
Endpoint requirements:
- HTTP/HTTPS
- Receives POST method
- Returns 2xx status
- Support TLS 1.2 (self-signed or expired certificates are not accepted)
- Rate dependent on merchant checkout traffic
- Inbound IP is dynamic
- Content-Type:
application/x-www-form-urlencoded
- Accept-Encoding: gzip
Webhook Request Properties:
- POST method
- User-Agent: Affirm-Webhook
- Origin IP is dynamic
- Number of fields varies; is based on:
- Event type - approved/declined/more_information events have conditional fields
- User opt-in - dictates whether conditional fields are present
Respond to a webhook event:
Your endpoint should respond to Affirm with a 2xx status code to communicate that you successfully received the webhook event notification. Affirm will treat all other response types as a failed delivery, including 3xx status codes. This means that we will treat URL redirections and "Not Modified" responses as failures. Affirm will ignore any other information returned in the request headers or request body.
Affirm doesn't currently resend a webhook event if your endpoint didn't successfully receive it.
Security Considerations:
Authentication
We can support Basic Authentication in the URL. You'll need to provide your endpoint URL username and password to your Client Success Manager.
Example: AB123:<[email protected]>/affirm_webhook
Signed Requests
Affirm signs webhook requests so you can optionally verify that Affirm is sending the request instead of a third-party pretending to be Affirm. Each request includes a base-64 encoded header, X-Affirm-Signature. The value of the header is an HMAC-SHA256 hash signature computed from the request payload and your private API key found in your merchant dashboard.
Replay attack prevention
If a third-party intercepts a request payload and its signature, your endpoint is susceptible to a replay attack. To mitigate these attacks, we include a timestamp in the X-Affirm-Signature header. Since the timestamp is part of the signed payload, the attacker cannot change the timestamp without invalidating the signature. If the signature is valid but the timestamp is old, you should reject the request. We recommend rejecting a response with a timestamp 5 minutes older than the current time.
Checkout webhook request body
Content-Type: application/x-www-form-urlencoded
checkout_token=I97HK0EREM38YHK3
created=2019-02-27T22%3A50%3A52.601851
event=confirmed
event_timestamp=2019-02-27T22%3A51%3A57.941799
order_id=000000017
webhook_session_id=A1b2C3
checkout_token | string | When the user exits, cancels, or is declined in the Affirm checkout flow |
event | enum | opened approved not_approved more_information_needed * confirmed |
event_timestamp | timestamp | UTC (YYYY-MM-DDTHH:MM:SS:SSSSSS) |
created | timestamp | UTC (YYYY-MM-DDTHH:MM:SS:SSSSSS) |
order_id | string | *(Optional) Must be enabled by Affirm. Only included if merchant passes the value in the checkout object |
webhook_session_id | string | *(Optional) Must be enabled by Affirm. Only included if merchant passes the value in the checkout object |
The
order_id
andwebhook_session_id
fields are unavailable in Shopify.
Reconciliation
You can reconcile with your own customer records based on any of these fields:
- Checkout_token - provided by Affirm
- Order ID - provided by the Merchant
- Webhook Session ID - provided by the Merchant
The
order_id
andwebhook_session_id
fields are unavailable in Shopify. Please reach out to your Client Success Manager at Affirm. They can provide access to the customer email address to be received in the webhook for reconciliation.
If using an Affirm-provided field, such as the checkout_token, you would need to store this ID in your system to later reconcile against incoming webhook requests.
If using one of your known fields (order_id, customer email, webhook_session_id), you should be able to reconcile the incoming webhook requests with the information you have on-hand.
The order_id
and webhook_session_id
sent in the checkout object is in all incoming webhook requests for that checkout attempt. You can reconcile this data with your customer records.
Example code
//-------------------------
// START AFFIRM MFP EXAMPLE
var toInteger = function(a) {
var _a;
if (typeof a === "number" && a != Math.floor(a)){
return a * 100;
}
if (typeof a === "number" && a === Math.floor(a)) {
return a;
}
if (typeof a === "string" && a.includes('.')) {
var _a = a.replace(/[$\.\,]/gi,'');
return parseInt(_a);
}
if (typeof a === "string" && a.includes('$')) {
var _a = a.replace(/[$\,]/gi,'') + '00';
return parseInt(_a);
}
else {
return parseInt(a);
}
}
var affirm_mfp_config = {
// Price threshold
"min": 30000,
// Put your MFP Promo ID below:
"promo_id": "",
// Put your MFP Financing Program name below:
"program_name": "",
"default_promo_id": "",
"default_program_name": ""
};
// Your existing price element's ID (optional)
var affirm_price_elid = "the-price";
// A delay to let things load (optional)
var affirm_trigger = "refresh-button";
if(affirm_trigger){
document.getElementById(affirm_trigger);
affirm_trigger.addEventListener('click',function(){affirmRefresh(affirm_current_price)});
}
var affirm_program_name = "";
var affirm_current_price = function() {
var _current_price;
var _affirm_promos = document.getElementsByClassName('affirm-as-low-as');
var _price_el = document.getElementById(affirm_price_elid);
if (!_price_el) {
console.log("No price provided");
return false
}
if (_price_el.tagName === "INPUT"){
_current_price = toInteger(_price_el.value);
}
else {
_current_price = toInteger(_price_el.innerText);
}
return _current_price
}
var affirm_mfp_eligible = function(a) {
return affirm_current_price >= affirm_mfp_config.min
}
function affirmRefresh(a) {
var _affirm_promos = document.getElementsByClassName('affirm-as-low-as');
for (var i = _affirm_promos.length - 1; i >= 0; i--) {
_affirm_promos[i].setAttribute('data-amount',affirm_current_price());
}
affirmMfp(a);
affirm.ui.refresh();
}
function affirmMfp(a) {
var _affirm_promos = document.getElementsByClassName('affirm-as-low-as');
if (affirm_mfp_eligible(a)) {
console.log('mfp');
for (var i = _affirm_promos.length - 1; i >= 0; i--) {
_affirm_promos[i].setAttribute('data-promo-id',affirm_mfp_config.promo_id);
}
affirm_program_name = affirm_mfp_config.program_name;
}
else {
for (var i = _affirm_promos.length - 1; i >= 0; i--) {
console.log('default');
_affirm_promos[i].setAttribute('data-promo-id',affirm_mfp_config.default_promo_id);
}
affirm_program_name = affirm_mfp_config.default_program_name
}
}
// affirm_checkout_object = {
// ...
// "financing_program_name" : affirm_program_name;
// ...
// }
// END AFFIRM MFP EXAMPLE
//-------------------------
Updated about 1 year ago