# Issuing real-time authorizations Learn about real-time authorizations. Your synchronous webhook is only used for authorisation requests. All other notifications are sent to your regular webhook endpoint. [Monitor your webhook responses](https://docs.stripe.com/issuing/controls/real-time-authorizations.md#monitor-your-webhook-responses) to ensure integration health. Using the synchronous webhook, you can approve or decline authorization requests in real time. Your webhook endpoint can be configured in your [settings](https://dashboard.stripe.com/account/issuing). When a card is used to make a purchase, Stripe creates an `issuing_authorization.request` and sends it to your configured endpoint for your approval. Get started with our [interactive guide to real-time authorisations](https://docs.stripe.com/issuing/controls/real-time-authorizations/quickstart.md). ## Responding to authorization requests You can respond to authorisation requests by responding directly to the webhook event. ### Respond directly Respond to the `issuing_authorization.request` webhook event directly to either approve or decline an authorisation after it’s received. #### Webhook response Our webhook accepts `JSON` responses with the following parameters: **Status code:** Return `200` to indicate success. **Header:** | field name | required or optional | description | | ------------------ | -------------------- | ----------------------------------------------------------------------------------------- | | **Stripe-Version** | required | See [API Versioning](https://docs.stripe.com/api/versioning.md) for supported values. | | **Content-Type** | optional | The only content type accepted for Authorisation webhook responses is `application/json`. | **Body:** | field name | required or optional | type | description | | -------------------------------------------- | -------------------- | ----------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **approved** | required | Boolean | Set `true` to approve an authorisation and `false` to decline. | | **amount** | optional | Integer | If the authorisation’s `pending_request.is_amount_controllable` property is `true`, you can provide this value to control how much to hold for the authorisation. It must be positive. | | **metadata** | optional | Set of [key-value pairs](https://docs.stripe.com/api/metadata.md) | This can be useful for storing additional information about the object in a structured format. | | **send\_fraud\_challenges** (Public preview) | optional | Array of strings | You can send a fraud challenge for this authorisation only through SMS. Leave it blank if you don’t want to send a challenge. | #### Ruby ```ruby # 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. Stripe.api_key = '<>' # 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_authorization.request' auth = event['data']['object'] # ... custom business logic status 200 header 'Stripe-Version' => '2025-03-31.basil', 'Content-Type' => 'application/json' data = { 'approved' => true } body data.to_json end # ...handle other cases end ``` ### Make an API call (Deprecated) This documentation is maintained for existing users. If you’re a new user, respond directly to the webhook. If you’re an existing user, plan to migrate to the direct webhook response. You can follow [our direct webhook migration guide](https://docs.stripe.com/issuing/controls/real-time-authorizations/direct-webhook-migration.md). Make an API call to either [approve](https://docs.stripe.com/api/issuing/authorizations/approve.md) or [decline](https://docs.stripe.com/api/issuing/authorizations/decline.md) the request and include the [Authorisation](https://docs.stripe.com/api/issuing/authorizations/object.md) ID. If you use this method, your webhook must approve or decline each authorisation before responding to the incoming webhook request. #### Ruby ```ruby # 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. Stripe.api_key = '<>' # Uncomment and 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_authorization.request' auth = event['data']['object'] handle_authorization(auth) end status 200 enddef handle_authorization(auth) # Authorize the transaction authorization = Stripe::Issuing::Authorization.approve(auth["id"]) end ``` We recommend that you only use one of these two methods to respond to authorisation requests. For users migrating from one method to another, both methods are supported during a migration. In the event both methods are used on the same authorisation, the API call takes precedence over the direct response. For migrations, we recommend only using one method on a given request at a time. If Stripe doesn’t receive your approve or decline response or request within 2 seconds, the `Authorization` is automatically approved or declined based on your [timeout settings](https://dashboard.stripe.com/account/issuing). > If your Issuing balance has insufficient funds for the incoming authorisation, the authorisation will be denied and your webhook endpoint won’t receive the `issuing_authorization.request` event. To learn more about funding your Issuing balance, [read here](https://docs.stripe.com/issuing/funding/balance.md). ## Authorization requests When an authorization request is sent to your webhook, the `amount` requested is stored in `pending_request`. ```json { "id": "iauth_1CmMk2IyNTgGDVfzFKlCm0gU", "object": "issuing_authorization", "approved": false, "amount": 0, "currency": "usd", "status": "pending", ... "pending_request": {"amount": 400, "currency": "usd", "merchant_amount": 360, "merchant_currency": "gbp" } } ``` The top-level `amount` in the request is set to 0 and `approved` is false. Once you respond to the request, the top-level `amount` reflects the total amount approved or declined, the `approved` field is updated, and `pending_request` is set to null. ### Testing webhooks locally To test webhooks locally, you can use [Stripe CLI](https://docs.stripe.com/stripe-cli.md). Once you have it installed, you can forward events to your server: ```bash stripe listen --forward-to localhost:4242/webhook Ready! Your webhook signing secret is '{{WEBHOOK_SIGNING_SECRET}}' (^C to quit) ``` In another terminal, you can then manually trigger `issuing_authorization.request` events from the CLI for more streamlined testing. ```bash stripe trigger issuing_authorization.request ``` Learn more about [setting up webhooks](https://docs.stripe.com/webhooks.md). ## Monitor your webhook responses Monitor your webhook responses for timeouts and errors by subscribing to the `issuing_authorization.created` and `issuing_authorization.updated` webhook events. When the [request_history.reason](https://docs.stripe.com/api/issuing/authorizations/object.md#issuing_authorization_object-request_history-reason) property is either `webhook_error` or `webhook_timeout`, Stripe isn’t receiving your responses. Don’t rely on `approved` to gauge integration health because Autopilot can approve on your behalf during an error condition. For example, investigate if an authorisation has `approved: true` but `request_history.reason: webhook_error`. We surface detailed error messages in the [request_history.reason_message](https://docs.stripe.com/api/issuing/authorizations/object.md#issuing_authorization_object-request_history-reason_message) field. ## Autopilot (Public preview) Autopilot provides fallback options that allow you to continue making real-time authorisation decisions when your systems are down, don’t respond to an authorisation request, or provide an invalid response. Autopilot makes an authorisation decision on your behalf based on a predefined set of rules. We create `Authorization` objects for transmission to reconcile Autopilot transactions. When Autopilot approves or declines an authorisation, the [request_history.reason](https://docs.stripe.com/api/issuing/authorizations/object.md#issuing_authorization_object-request_history-reason) property of the `issuing_authorization.created` webhook is either `webhook_error` or `webhook_timeout`: - `webhook_error` if you respond to the real-time authorisation webhook with an invalid [Stripe API version](https://docs.stripe.com/api/versioning.md) in the [request headers](https://docs.stripe.com/issuing/controls/real-time-authorizations.md#respond-directly), or if we can’t process your response. - `webhook_timeout` for all other failure modes. To configure Autopilot, [contact Stripe support](https://support.stripe.com/contact/login), and [monitor](https://docs.stripe.com/issuing/controls/real-time-authorizations.md#monitor-your-webhook-responses). ## Stripe Autopilot (Public preview) For users with their own dedicated Bank Identification Numbers (BIN), Stripe Autopilot can help make authorisation decisions when the card network can’t reach Stripe. When an authorisation is approved or declined through Stripe Autopilot while Stripe is down, the [request_history.reason](https://docs.stripe.com/api/issuing/authorizations/object.md#issuing_authorization_object-request_history-reason) property of the `issuing_authorization.created` webhook is `network_fallback`. To configure Stripe Autopilot, [contact Stripe support](https://support.stripe.com/contact/login), and [monitor](https://docs.stripe.com/issuing/controls/real-time-authorizations.md#monitor-your-webhook-responses). ## Fraud challenges through webhooks (Public preview) [Fraud challenges](https://docs.stripe.com/issuing/controls/fraud-challenges.md) allow your cardholders to retry non-fraudulent transactions that would have otherwise been blocked. While fraud challenges are generally available, the ability to trigger them yourself through real-time webhook responses is in public preview. To manage the rules that dictate when a fraud challenge is sent, adjust your response to the `issuing_authorization.request` webhook. You can trigger fraud challenges in scenarios where you detect spending that appears suspicious and want additional verification (for example, a cardholder using their card outside the country). To do so, decline the `issuing_authorization.request` webhook and include the `send_fraud_challenges` field with the `["sms"]` value. Triggering fraud challenges through real-time webhook responses is currently limited to preview users. You must be an Issuing customer to join the preview. To request access to the preview, log in to your Stripe account and refresh the page. [Contact Stripe](https://stripe.com/contact/sales) for more information.