Issuing real-time authentications
Learn how to set up an integration to respond to real-time Issuing authentication webhooks.
Availability
Real-time authentication decisions are available only for eligible, supported Issuing integrations. Contact your Stripe account manager or Stripe Support to confirm eligibility and request access.
You can decide to proceed with an approval, decline, or cardholder challenge using the synchronous webhook.
You can configure your webhook endpoint in your Dashboard settings. When a user uses a card to make a purchase, Stripe creates an issuing_authentication.decision event and sends it to your configured endpoint for your approval.
Authentication requests
Here is an example of what the issuing_authentication.decision synchronous webhook might contain:
{
"id": "iauthn_1CmMk2IyNTgGDVfzFKlCm0gU",
"object": "issuing.authentication",
"card": "{{CARD_ID}}",
"cardholder": "{{CARDHOLDER_ID}}",
"challenge": null,
"merchant_amount": 50000,
"merchant_currency": "usd",
"status": "pending_challenge_decision",
...
}
Respond to authentication requests
You can respond to authentication requests by responding directly to the webhook event. Respond to the issuing_authentication.decision webhook event directly to either approve, decline or proceed with a challenge for an authentication after it’s received.
Webhook response
Our webhook accepts JSON responses with the following parameters (a 200 response status code indicates success):
Header
| field name | required or optional | description |
|---|---|---|
| Stripe-Version | Required | Version in the related setting-dd-mm format. |
| Content-Type | Optional | The only content type accepted for Authentication webhook responses is application/json. |
Body
| field name | required or optional | type | description |
|---|---|---|---|
| challenge | Required | Boolean | To contain the decision field. |
| decision | Required | Enum | Determines how to proceed with the authentication. One of challenge_cardholder, approve_without_challenge or decline_without_challenge. |
| metadata | Optional | Set of key-value pairs | This is useful for storing additional information about the object in a structured format. |
Handle webhooks from Stripe
Learn how to properly respond to the webhook and how the webhook outcome reflects onto the authentication object.
Read the event data
Stripe sends the event data in the request body. Each event is structured as an Event object with a type, id, and related Stripe resource nested under data.
Handle the event
As soon as you have the event object, check the type and filter for issuing_authentication.decision. This is the webhook event that Stripe sends when 3DS is requested. Write your business logic here to make a decision for the incoming authentication. For example, you can challenge any authentication over a certain merchant_amount.
Respond to the webhook
The issuing_authentication.decision webhook is synchronous, which enables you make a decision per authentication in real time. You can include your decision in the response body of the webhook request. To do so you must also specify the Content-Type header as application/json.
After Stripe processes the response, we fire a discrete issuing_authentication.updated event. In this, the top-level challenge in the request turns to null, and status becomes pending_challenge_decisions.
When you answer the request, the top-level challenge.decision reflects your challenge decision—either approve_without_challenge, decline_without_challenge, or challenge_cardholder. Accordingly, status changes to approved, failed, or requires_api_approval, and challenge.decision_reason gets filled with webhook_response
Here’s an example integration for a local endpoint at http://localhost:4242/webhook:
server.rb
Select a language
Ruby
Python
Node.js
No results
# Using Sinatra.
require 'sinatra'
require 'stripe'
set :port, 4242
# Don't put any keys in code. See https://docs.stripe.com/keys-best-practices.
# Find your keys at https://dashboard.stripe.com/apikeys.
client = Stripe::StripeClient.new('sk_test_BQokikJOvBiI2HlWgH4olfQ2')
# Replace with a real secret. You can find your endpoint's secret in your webhook settings.
webhook_secret = 'whsec_...'
post '/webhook' do
payload = request.body.read
sig_header = request.env['HTTP_STRIPE_SIGNATURE']
event = nil
# Verify webhook signature and extract the event.
begin
event = Stripe::Webhook.construct_event(
payload, sig_header, webhook_secret
)
rescue JSON::ParserError => e
# Invalid payload.
status 400
return
rescue Stripe::SignatureVerificationError => e
# Invalid signature.
status 400
return
end
if event['type'] == 'issuing_authentication.decision'
auth = event['data']['object']
# ... custom business logic
status 200
header 'Stripe-Version' => '2022-08-01', 'Content-Type' => 'application/json'
data = { 'challenge' => {'decision' => 'challenge_cardholder'} }
body data.to_json
end
# ...handle other cases
end
Webhook timeouts and errors
User timeout
If Stripe doesn’t receive your response within three seconds, the authentication proceeds with cardholder_challenge and webhook_timeout events, and a challenge.decision_reason.
Invalid response
When the response sent through the synchronous authentication webhook is invalid, Stripe populates challenge.decision_reason with webhook_error. The challenge.decision_reason_message field contains a more detailed description of the error.
Stripe error
If Stripe can’t process your response successfully, the authentication proceeds with a cardholder_challenge. In this case, Stripe sets challenge.decision_reason to stripe_error.
