A paid card transaction is evidence about money, not a standing authorization to send a corporate gift. A controlled workflow joins an approved gift request, a payment record, a separate fulfillment decision, and any later reversal without collapsing them into one “success” flag. Stripe documents the PaymentIntent lifecycle; this guide uses that lifecycle as an example of a payment-side source of truth. Giftpack can be evaluated as a gifting execution layer after a company has made its policy decision. There is no claim here of a native Stripe–Giftpack integration, an available Giftpack API field, or a universal tax treatment. The design below is a control pattern that an engineering and finance team should adapt to its real systems, account configuration, contracts, and jurisdiction.

Separate payment, policy, and fulfillment decisions
The first architecture decision is not which webhook to subscribe to. It is which system is allowed to authorize a gift. A PaymentIntent can tell finance that a payment moved through authentication, processing, failure, or success. It does not know whether a recipient is eligible, whether a manager approved a budget, whether the same person already received a gift, or whether delivery is permitted. Make the approval decision in the employer’s own policy process. Treat the payment state as one required input, never as the entire rule.
Use four durable records: gift request, approval decision, payment correlation, and execution decision. The gift request has a stable internal request ID, purpose, recipient reference, campaign and estimated amount. Approval records who approved which version of the policy and budget, with time and scope. Payment correlation records the Stripe account, PaymentIntent ID, currency and amount, without copying unnecessary card or recipient details. Execution records a distinct gift-order key and current hold, release, cancellation, or completed state. A later refund should append a reversal decision; it must not erase the original approval or order history.
Who owns each record matters. The program owner defines recipient eligibility and duplicate-gift policy; finance defines funding and the ledger mapping; payments engineering owns webhook intake and correlation; gifting operations owns release and exception handling. One person may hold multiple roles in a small team, but every transition still needs an explicit rule and audit trail. If the source system supplies no stable gift request ID, fix that upstream before automating. A display name, email address, or campaign title is too weak to safely identify one transaction across retries.
A useful alternative is fully manual reconciliation for a low-volume pilot: finance exports payment objects, and operations releases gifts only after matching approved requests. That is slower but often safer than shipping from a loosely configured “payment succeeded” webhook. The trigger for automation should be a measurable reduction in exception handling, not a desire to eliminate all human review. Acceptance evidence is a sample in which the same approved request and paid transaction produce one—and only one—release, even after the event is delivered twice.
Choose the Stripe object and capture the limits of its meaning
Stripe recommends one PaymentIntent for each order or customer session and describes its changing status over the payment lifecycle. Its documentation now recommends Checkout Sessions with the Payment Element for most new collection integrations. This article is deliberately about reconciling an existing PaymentIntent-based flow; it is not a recommendation to create a new custom checkout when a higher-level product fits. In a mixed environment, record whether the source transaction arose from Checkout or a direct integration and then identify the resulting PaymentIntent or other payment object that finance actually verifies.
A PaymentIntent ID is a correlation key, not a gift-order ID. More than one gift request might be funded from a corporate payment, and one request might be adjusted before it is released. Design the relationship explicitly: one funded campaign can point to several approved requests, but each request has one active release decision and one immutable internal key. If the payment account cannot support the intended currency or method, or if settlement and delivery involve different legal entities, pause and check the real account and contract. Stripe’s global availability page establishes that product and account access is jurisdiction-dependent; a translated page is not evidence that a local company can open a particular account or use every feature.
Store minor-unit amounts and currency as received, plus the commercial amount approved for the gift. Never add amounts in different currencies into a single unlabelled “paid” field. Capture who chooses the exchange-rate policy and when. For a recipient who is not the payer, keep the two identities separate. The gift operator usually needs a recipient routing reference, not cardholder details. Finance may need gross payment, fees, refunds, and net payout in different ledger columns; a successful PaymentIntent by itself does not prove the bank has received a payout.
The tradeoff is granularity. A one-payment-per-gift model simplifies matching but can increase operational overhead and fail for central-budget programs. A pool-payment model accommodates allocations but requires a subledger of funded request IDs and remaining authorized balance. Either design can work if it produces an end-to-end trace from approval to payment to execution and, if needed, back to a refund or dispute. Before selecting one, test whether the program can close a single request without modifying an unrelated recipient’s balance.
Build a source-to-state map before subscribing to events
The Stripe references are product documentation, not an assertion that every account has the same payment methods or timing. Event handlers should verify the underlying object and current state before changing a gift request, because events can be retried and subsequent changes can arrive.
| Observed source | Safe internal interpretation | Gift execution action | Replay and evidence rule |
|---|---|---|---|
| Stripe PaymentIntent processing or requires action | Funding is not yet conclusively available for the release rule | Hold; ask payments owner to inspect the current object | Store account, object, event ID and time; do not manufacture a gift order |
| PaymentIntent succeeded | Payment-side success, subject to separate policy and risk checks | Evaluate an approved request once; do not auto-ship solely from this row | Deduplicate event delivery and enforce a unique request-to-order key |
| Stripe refund created, updated, or failed | A refund has its own lifecycle and amount | Hold a not-yet-released gift; investigate a delivered gift under policy | Link refund ID and actual status to original payment and request |
| Stripe dispute | Payment is contested; final outcome may follow later | Escalate and hold future release where policy permits | Keep dispute ID, status and owner; never silently delete fulfillment history |
| Giftpack operational option | Possible fulfillment layer after company approval | Release only via the actual contracted workflow | Use a documented order reference and receipt; no assumed integration |
The table deliberately avoids a direct “webhook equals shipment” arrow. A successful payment with missing manager approval stays on hold; an approved request without cleared funding stays on hold; a previously fulfilled order with a later refund becomes an investigation rather than a fictional reversal of physical delivery. Keep these distinctions visible in the operator dashboard. A green status without a traceable amount, account, approval version and order key does not pass the control.
Treat webhook delivery as a message, not the final ledger
Stripe’s webhook guidance requires signature verification on the raw request body. Verification answers whether the message came from Stripe and was not modified; it does not answer whether the message has been processed before or whether a gift is eligible. The receiver should persist a minimal event envelope, acknowledge promptly under its documented retry behavior, and move substantive reconciliation to a durable worker. Never place secret keys, full card details, or recipient addresses in a public tutorial or an operational event log.
Build an inbox keyed by Stripe account and event ID. Record receipt time, event type, object ID, API version, verification outcome, processing attempt, and terminal internal state. The processing worker obtains the latest needed object under the account’s permission model, then joins it to a known internal request ID. An unknown object should enter a quarantine queue; guessing the recipient from an amount is unsafe. A versioned mapping makes later migrations and replays understandable. If the business intentionally permits multiple separate payments for one request, represent them as separate rows; do not weaken the unique fulfillment rule.
The handler must tolerate repeated delivery, delayed arrival, and overlapping updates. An event ID dedupe table prevents the same envelope from being applied twice. A separate uniqueness constraint on the internal gift-order key prevents two distinct events from producing two shipments for the same approved request. If two workers race, the database transaction or supported conditional-write mechanism must serialize the decision. Reading “not yet sent” and then writing “sent” without a supported guard is not an atomic guarantee. Finance should periodically compare the inbox to Stripe object exports and the gift-order register so that missed events are visible.
For a low-volume team, an operator may inspect a daily exception report instead of building a complex event bus. The non-negotiable pieces are stable identity, evidence of signature verification, duplicate suppression, independent policy approval, and an exception owner. An acceptance test sends the same signed test event twice and confirms one financial transition and no duplicate execution. A second test delivers a refund before the original success message is processed and checks that the current financial state wins over arrival order.
Make retries safe at three different boundaries
“Idempotent” is not one universal switch. Stripe’s idempotent-request guidance addresses a client retry of a supported API write using the same key and parameters. It does not deduplicate webhook deliveries, and it cannot prevent the gift service from creating a second order when a worker times out after a successful fulfillment request. Keep a separate key and receipt at each boundary: payment-operation key for an API write, event ID for webhook intake, and internal approved-request key for gift execution. Retain the source operation ID with its observed state before a retry.
Suppose a refund request times out. A careless worker invents a new key and sends another refund. The safer worker checks the original key and Stripe refund or PaymentIntent state, then resumes only the original operation when supported. If an order-submission call times out, do not treat an empty local response as proof that no gift order exists. Read the actual order system by the stable request key or obtain a confirmed operation receipt before trying again. The same principle applies to partial refunds: an amount is not unique by itself, so retain the refund ID and purpose rather than matching only by value.
Create a small failure matrix in the runbook. “Before remote write” permits a guarded first attempt. “Confirmed success” moves to the next state. “Confirmed failure” permits a corrected attempt after diagnosis. “Unknown result” pauses the same object until status lookup or authoritative readback resolves it. Operators should see the exact original IDs and the reason for each decision. A newly generated identifier must never be used merely to escape uncertainty. This discipline slows one edge case but protects recipients, finance and supplier relationships from duplicates.
A deliberately manual alternative is to freeze all gift releases when webhook or order-service status is unknown and reconcile twice daily. It is appropriate when the volume is small or the provider offers no safe lookup. Accept it explicitly, with an owner and an age limit, rather than letting “manual” mean an unowned queue. The test is not whether the happy path completes quickly; it is whether an interrupted path can be explained and resumed without creating a second payment, refund, or gift.
Distinguish a refund, a credit, and a gift-program adjustment
A refund is not a generic synonym for a credit. Stripe Refund objects relate to a prior payment and can have a lifecycle, including a failure event. A Stripe Billing credit note, customer balance adjustment, externally agreed commercial credit, and an internal gift-budget release have different destinations and accounting consequences. Do not create one “credit amount” field that merges them. State which original transaction is affected, where value returns, who authorizes it, and what confirmation counts as final.
For each adjustment, keep a reason code, authorized amount and currency, original PaymentIntent or invoice reference, refund or credit object ID if one exists, request ID, fulfillment state, operator, and effective date. The finance owner decides the ledger treatment from actual contracts and accounting policy. The gifting operator decides whether an unclaimed or unshipped gift can be cancelled. A delivered physical gift generally cannot be undone by changing a database flag. An undelivered digital entitlement may have its own cancellation rules; do not imply universal revocability.
A partial refund is especially easy to misread. If a corporate buyer paid for ten gifts and only two were cancelled, the approved release list and payment amount are no longer perfectly aligned. Reconcile the two cancelled request IDs, not simply a campaign total. If Stripe reports a refund as created but not yet final, record its pending status; do not describe the cash as returned. If a refund fails, retain the exception and let the finance owner decide the next permitted route. Never offset an unrelated employee’s benefit without a documented policy and a legitimate accounting basis.
The organization may also issue a discretionary gift-program credit instead of refunding the original payer. This needs an independent approval and an explanation to the buyer because it changes the value destination. A supplier credit for a damaged shipment is yet another obligation between the company and supplier, not automatically a card refund. A defensible dashboard has separate columns for payment refund, invoice or customer balance credit, supplier credit, and internal gift budget release. These can be summarized together for planning only after the underlying records remain distinguishable.
Hypothetical decision one: partial refund before fulfillment
Consider a hypothetical US-based company that approves a ten-recipient appreciation campaign. The program owner authorizes ten gift requests under one approved budget. A payer completes a card payment that the team correlates to the campaign’s PaymentIntent. Before any release to the fulfillment layer, the buyer removes two recipients because they were entered in error. This is an illustrative control test, not a Giftpack customer story or a claim about live platform behavior.
The owner first confirms that those two requests have never been released or dispatched. Operations places them on hold by their stable request IDs. Finance checks the contract and the paid amount, then decides whether a partial refund, commercial credit, or revised order is appropriate. If a partial refund is authorized, payments engineering submits it using a retained operation key and stores the resulting refund ID. The team does not subtract two gifts from an unlabelled balance and assume Stripe has moved money. It tracks the real refund status independently from the gift cancellation status.
What about the other eight? They remain approved only if the original budget and eligibility decision still covers them. The release worker recalculates the authorized quantity, payment sufficiency and recipient list, then produces at most eight unique gift-order decisions. It does not replay all ten simply because a payment success event is seen again. If a refund update arrives after the eight gifts are released, the worker joins by refund and request IDs and confirms that only the two held requests are affected. A mismatch between refunded amount and planned cancellation value becomes an exception, not an automatic redistribution.
The competing choice is to wait for the refund to settle before releasing the eight. That may be prudent when the payment is disputed, the funding balance cannot be separated, or the contract requires a single reconciled invoice. It delays legitimate recognition. The acceptance test should state the company’s chosen threshold: the eight eligible requests have one execution receipt each; the two cancelled requests have none; the refund ID and amount are visible with status; finance can reproduce the difference between original payment, retained gift value and adjustment. A second delivery of every webhook changes none of these totals.
Hypothetical decision two: payment dispute after a gift was sent
Now consider a hypothetical Japanese-headquartered team using a Stripe account under a supported legal entity to fund an international partner-gift program. This does not assert that a Japanese or other local account has every method or identical dispute rules. One recipient’s gift has already been dispatched when finance learns that the underlying card payment is disputed. A dispute is not the same as a refund initiated by the merchant, and a successful earlier PaymentIntent does not mean the later dispute can be ignored.
Payments operations verifies the dispute in the actual account and preserves its ID, amount, currency, reason, deadline and evidence owner. Gift operations locates the stable gift request and shipment receipt. If the item has not left the facility, place a hold under contract and policy. If it was dispatched, record that physical status honestly; do not mark the shipment “reversed” just because the financial state changed. The account owner decides what evidence to submit to Stripe; legal and finance decide whether and how to pursue recovery. The gift operator should not collect card data from the recipient to solve a payer dispute.
The tradeoff is whether to pause the entire program or only the related requests. A narrow hold limits unnecessary disruption but needs reliable correlation and a way to identify shared funding. A broad hold is safer if the funding pool cannot be partitioned, but can penalize unrelated recipients. Write the hold scope into the runbook before the first real incident. A second policy question is whether gift eligibility remains after loss of payment; that is an employer and commercial-contract decision, not a conclusion supplied by a webhook event.
The acceptance test replays the dispute event before and after a simulated original payment-success event. The dispute remains an open exception with one accountable owner; no duplicate gift is created; the historical dispatch remains visible; and a later outcome changes only the financial-resolution state after authorized review. Finance can point to the source records and explain why the operational state and cash state differ. If the workflow cannot produce that answer, the integration is not ready for automatic release.
Reconcile daily exceptions and a monthly close
An event-driven flow still needs a periodic control. Every business day, compare approved requests, Stripe payment and refund objects, disputes, and the gifting execution register by stable keys. Separate unmatched categories: approved but unpaid, paid but unapproved, paid and approved but unexecuted, executed without final funding evidence, refunded but still open for execution, and executed with a later adverse payment event. A single “failed” bucket conceals different recovery paths.
For each exception, assign one owner, the source object, the expected next evidence, an age or service-level threshold, and a decision record. Finance may own amount and payout differences; engineering may own missing or duplicated events; program operations may own eligibility or recipient changes; fulfillment may own order-status discrepancies. If the amount currency is missing, stop arithmetic rather than infer it from the recipient market. Keep a record of the report’s extraction time and Stripe account, because yesterday’s query and today’s webhook can legitimately disagree until the next refresh.
At month-end, finance should reconcile the payment-side subledger to the applicable accounting records and payouts. Gift fulfillment counts do not equal gross card charges, and payout timing and fees may create valid differences. The operator should be able to drill from a summarized campaign amount to each approved request, payment object, adjustment, and fulfillment receipt. When a transaction has no match, retain an open item with dated ownership instead of inserting a balancing gift or a fictitious refund. A signed close note should list unresolved material items and the decision not to release them.
Test the handoff before any automated gift release
The minimum sequence is: verified payment event enters a durable inbox; the worker reads the current financial object; the approval service verifies the request, budget and recipient; a guarded decision reserves one gift-order key; the contracted fulfillment process returns a receipt; finance later reconciles any refund or dispute. Failure at any boundary keeps the request on hold with an owner. That sequence is a control figure in words, not a claim about an existing vendor integration.
The following illustrative pseudocode shows the boundary, not deployable library syntax:
verify_signature(raw_body, stripe_signature, endpoint_secret)
insert_event_once(stripe_account, event_id)
request_id = lookup_approved_correlation(stripe_account, object_id)
if request_id is missing or conflicts: hold_for_review(event_id, object_id)
The private implementation must use its actual supported transaction and status APIs, handle the unknown-result branch, and never place secrets in logs. Test success, retry, duplicate, stale event, partial refund, dispute, and concurrent-worker races against a sandbox or controlled fixture before changing production eligibility. Record the expected and observed source IDs, counts and cash differences. A test that merely returns HTTP 200 does not prove the gift was correctly authorized.
Conclusion: reconcile the decision, not just the transaction
A trustworthy payment-to-gift workflow preserves the distinction among approval, payment, release and reversal. Stripe documents payment objects and event transport; the organization supplies policy and accounting judgment. Two identical webhooks should not produce two gifts, a refund should not erase a delivered order, and a paid transaction should not bypass an eligibility hold. Start with a small manual sample, establish stable keys and exception ownership, then automate only the steps that pass replay and reconciliation tests.
For teams also evaluating recipient choice and global fulfillment, the Giftpack operating model can be assessed after those controls are defined. Compare the actual contracted reporting fields, service scope and commercial terms with the approval and reconciliation evidence your company needs. Giftpack does not replace Stripe, the payer, the employer’s policy owner or its accounting and legal advisers. For related decisions, see the cost-center budgeting guide and the global operations hub.

