Skip to main content

Stripe Webhooks

GiveLink listens for Stripe webhook events to keep donation records in sync with payment status, handle recurring giving lifecycle events, and trigger downstream actions like receipt emails and dispute alerts.

Endpoint

POST /api/stripe/webhooks
This endpoint is called by Stripe, not by your application. Configure it in your Stripe Dashboard.

Events Handled

Payment Events

EventAction
payment_intent.succeededUpdates the donation status to SUCCEEDED. Increments donor and campaign raised/count aggregates. Queues a personalized receipt email to the donor. Sends an admin notification email to the org. Sends tribute notification emails if the donation has a dedication with notifyEmail. Triggers milestone alerts if the campaign crosses a goal threshold.
payment_intent.payment_failedUpdates the donation status to FAILED. Logs the failure reason from Stripe.
For recurring donations, Stripe fires both payment_intent.succeeded and invoice.payment_succeeded. GiveLink processes renewals exclusively via the invoice.payment_succeeded handler to prevent double-counting. The payment_intent.succeeded handler skips donations that are linked to a subscription.

Recurring Giving Events

EventAction
invoice.payment_succeededHandles recurring donation renewals. For subscription_create billing reason, marks the initial recurring donation as succeeded and records the Stripe subscription ID. For subscription_cycle billing reason, creates a new renewal donation record and updates aggregates.

Stripe Connect Events

EventAction
account.updatedChecks whether the Stripe Connect Express account has completed onboarding (charges enabled, payouts enabled). Transitions the org status from ONBOARDING to ACTIVE when fully verified.

Dispute Events

EventAction
charge.dispute.createdUpdates the donation status to DISPUTED. Sends a dispute alert email to the org’s notification email with the dispute amount, reason, and deadline to respond.
charge.dispute.closedUpdates the donation based on dispute outcome: SUCCEEDED if won, REFUNDED if lost. Logs the resolution.

Webhook Signature Verification

All webhook requests from Stripe include a Stripe-Signature header. GiveLink verifies this signature using the STRIPE_WEBHOOK_SECRET environment variable to ensure the request genuinely originated from Stripe. Requests with invalid signatures are rejected with 400 Bad Request.

Idempotency

GiveLink maintains an in-memory set of processed Stripe event IDs to prevent double-processing if Stripe retries a webhook event. Each event ID is processed at most once per server instance lifetime.

Setup

  1. In Stripe Dashboard, go to Developers > Webhooks
  2. Click Add endpoint
  3. Set the endpoint URL to: https://givelink-api-production.up.railway.app/api/stripe/webhooks
  4. Select the following events to listen for:
    • payment_intent.succeeded
    • payment_intent.payment_failed
    • invoice.payment_succeeded
    • account.updated
    • charge.dispute.created
    • charge.dispute.closed
  5. After creating the endpoint, copy the Signing secret
  6. Set it as the STRIPE_WEBHOOK_SECRET environment variable in your API deployment
The webhook endpoint must be publicly accessible — Stripe cannot reach localhost. Use a tool like Stripe CLI (stripe listen --forward-to localhost:3001/api/stripe/webhooks) for local development.