Stripe | Financial Infrastructure to Grow Your Revenue

Stripe | Financial Infrastructure to Grow Your Revenue

4466 articles

Issuing real-time authentications


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 namerequired or optionaldescription
Stripe-VersionRequiredVersion in the related setting-dd-mm format.
Content-TypeOptionalThe only content type accepted for Authentication webhook responses is application/json.

Body

field namerequired or optionaltypedescription
challengeRequiredBooleanTo contain the decision field.
decisionRequiredEnumDetermines how to proceed with the authentication. One of challenge_cardholder, approve_without_challenge or decline_without_challenge.
metadataOptionalSet of key-value pairsThis 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.

Last verified 2026-09-24

Is this helpful?