Choose your approach
Stripe Issuing supports two integration patterns depending on whether you need to show pending rewards before a transaction settles.
Start with settlement-only if you’re unsure. You can add authorization holds later without changing your existing rules: the settlement rules continue to work as-is when there are no held lots to reconcile.
Architecture
- Settlement-only
- Auth + Settlement
The middleware verifies the Stripe signature, extracts the fields Scrip needs, and forwards the event. No business logic lives here; all reward calculations happen in Scrip rules.What you need:
cardholder ID as the Scrip external_id. This means each Scrip participant’s external_id must match their Stripe cardholder ID:
metadata field is a good place to store your internal user ID.
Webhook setup
- Settlement-only
- Auth + Settlement
In the Stripe Dashboard or via the API, create a webhook endpoint that subscribes to Store the webhook signing secret (
issuing_transaction.created:whsec_...). You’ll use it to verify incoming requests.You only need
issuing_transaction.created. This single event covers both captures (settlement) and refunds. The transaction’s type field distinguishes them. Authorizations (issuing_authorization.*) are not settlement and can be reversed, expired, or never captured, which makes them unreliable for reward calculations. See Stripe’s transaction lifecycle for details.Middleware
You need a small service between Stripe and Scrip. It receives Stripe webhook events, verifies the signature, transforms the payload, and forwards it toPOST /v1/events.
- Settlement-only
- Auth + Settlement
The fields above are the ones used by the example rules in this guide. Your middleware can include any additional fields in
event_data that are useful for your program. Stripe’s Transaction object includes purchase details, network data, and more. Anything you add to event_data is available in rule conditions as event.<field_name>.Rules
With the middleware in place, Stripe events arrive with a consistentevent_data shape. The rules below implement cashback category multipliers and refund handling.
These snippets intentionally omit rule_set_id, so Scrip assigns them to the program’s automatic default set. Each stop_after_match applies within that set. Create the rules in the order shown: each new rule is appended to the end of its set, so creation order is evaluation order. Keep each ordered earning or reversal cascade in one set if you move it later.
- Settlement-only
- Auth + Settlement
Earning rulesAward cashback when The dining rule runs first and The reversal amount is calculated from the refund amount and MCC, using the same multiplier as the earning rule. Stripe includes the MCC on both captures and refunds, so the rates always match.
event_data.type is "purchase":stop_after_match prevents the later base rule in the same set from also firing. See Category multipliers for a deeper walkthrough.Reversal rulesWhen a cardholder receives a refund, the cashback they earned on that purchase should be clawed back. Reversal rules mirror the earning rules with DEBIT instead of CREDIT, using the same multiplier per MCC so the debit matches what was originally awarded:The refund rules above use
"allow_negative": true so the debit succeeds even if the cardholder has already spent their cashback. The balance goes negative and is offset by future earnings. Without allow_negative, a DEBIT fails when the available balance is insufficient, and the refund event would be marked FAILED. See Negative Balances for more on this behavior.
Add these to every earning rule’s condition, or create a
stop_after_match rule at the start of the same set that matches excluded MCCs and takes no actions. This stops the remaining earning rules in that set. Other rule sets still continue.
Transaction lifecycle
- Settlement-only
- Auth + Settlement
Authorizations are not final. They can be reversed, expired, partially captured, or over-captured. Awarding points on authorization would require reversing them on every one of these outcomes. Waiting for the
capture transaction avoids this complexity entirely.authorization field that references the original authorization ID. This link is present on both captures and refunds.
Your middleware passes
authorization_id through in event_data. You can reference it in rule conditions if needed. For example, to skip refund processing on force captures:
Stripe says linking refunds to authorizations is “an inexact science”. Some refunds arrive with
authorization: null. For category-based reward rates, this doesn’t matter. The refund carries its own MCC and the correct debit rate can be calculated independently. For more complex scenarios (threshold-based rates, tiered earn rates), see Edge cases.Edge cases
Partial refunds. Stripe refunds can be for less than the original capture amount. The refund transaction’samount reflects only the refunded portion, and your reversal rules calculate the debit from that amount. No special handling needed. A $40 refund on a $100 dining purchase debits $2.00 (5% of $40).
Multi-capture transactions. Airlines, hotels, and car rental companies can create multiple capture transactions against a single authorization. Each capture arrives as a separate issuing_transaction.created event with a unique transaction.id, so they map to separate Scrip events with distinct idempotency keys.
Force captures. Some merchants settle without a prior authorization (e.g., offline transactions). These arrive as type: "capture" with authorization: null. Award points normally. The transaction is still a valid settled purchase. For the auth + settlement approach, the settlement rules still work: when authorization_id is null, there are no held lots to reconcile, so the system performs a standard credit.
Refund reversals. Stripe can reverse a refund if it was issued in error. This appears as a transaction with type: "refund" and a negative amount. Your middleware should detect this case and map it to a "purchase" type instead:
event.original_earn_rate and use a priority refund rule that references it:
default set (use position when creating it, since a plain create appends to the end). It then matches first when original_earn_rate is present, and stop_after_match keeps the MCC-based refund rules from also firing. Those rules handle cases where it is absent.
Disputes. Stripe Issuing disputes follow a separate lifecycle. If a cardholder disputes a transaction and wins, the funds return to the issuing balance but no issuing_transaction.created event fires. Instead, Stripe emits issuing_dispute.* events. If you need to reverse points on successful disputes, subscribe to issuing_dispute.closed in your Stripe webhook and forward it as a refund event when dispute.status === "won". For many programs, dispute-based reversals aren’t necessary. The volume is low and the complexity isn’t worth it.
Uncaptured authorizations
Auth + settlement only. An authorization that is never captured (reversed by the merchant or expired by Stripe) does not produce anissuing_transaction.created event. Two approaches handle this:
Active void (recommended). The middleware and rules in this guide already handle this case. When authorization.status changes to reversed or closed, the middleware forwards a void event and the void_auth rule cancels the provisional rewards via VOID_HOLD. This removes the held balance from the cardholder’s view and returns value to the program wallet.
Passive expiration. If you choose not to subscribe to issuing_authorization.updated, the held lots created on auth have an expires_at (set to 720h in the example rules). They expire and are forfeited to breakage automatically. This is simpler but leaves stale held balances visible until expiration.
Testing
Stripe Issuing supports test mode. Create test cardholders and simulate transactions:issuing_transaction.created webhook event in test mode, which flows through your middleware to Scrip.
You can also test rules independently of Stripe by sending events directly to Scrip:
Example
- Settlement-only
- Auth + Settlement
A cardholder buys an $85 lunch at a restaurant (MCC 5812).
1
Cardholder pays at a restaurant
The card is charged $85. Stripe creates an authorization and holds the funds on the issuing balance. No event is sent to Scrip yet.
2
Merchant settles the transaction
The next day, the merchant captures the authorization. Stripe creates a Transaction object and fires
issuing_transaction.created:3
Middleware transforms and forwards
Your server receives the webhook, verifies the Stripe signature, and maps the payload to a Scrip event. The amount converts from cents to dollars (
-8500 → 85.00), the type maps from "capture" to "purchase", and the cardholder ID becomes the external_id:4
Scrip processes the event
dining_5pct rule matches (event.type == "purchase" and MCC 5812). Credits $4.25 (5% of $85). stop_after_match prevents the later base rule in the same set from also firing. Event status transitions to COMPLETED.5
Two weeks later, cardholder gets a partial refund
$40 refunded. Stripe fires another
issuing_transaction.created with type: "refund". Middleware forwards it. refund_dining rule matches and debits $2.00 (5% of $40).Next steps
This guide covers the core integration. To build on it:- Add spend thresholds, retroactive bonuses, and monthly resets with the Cashback Card example
- Browse more rule recipes like sign-up bonuses, streaks, and tiered earn rates in Common Patterns
- Learn how events flow through the processing pipeline in Event Processing
- Read about the auth / settlement pattern for a deeper look at how auto-reconciliation works under the hood