Stripe Integration
Connect Stripe for payment processing — accept card payments for door access and device usage.
Overview
Stripe handles all payment processing in Hygate. When a guest pays for access, Stripe charges their card and notifies Hygate via webhook. Hygate supports Stripe Checkout for a hosted payment experience, Stripe Payment Intents, and webhook-based event handling.
What You Need
- A Stripe account (stripe.com)
- API keys (Publishable Key + Secret Key)
- Webhook signing secret
Setup Steps
Step 1: Get Your API Keys
- Log in to Stripe Dashboard
- Go to Developers → API keys
- Copy the Publishable key (starts with
pk_) - Copy the Secret key (starts with
sk_)
Use test mode keys during development (prefix
pk_test_andsk_test_). Switch to live mode keys before going to production.
Step 2: Configure Webhooks
- Go to Stripe Dashboard → Developers → Webhooks
- Click Add endpoint
- Enter your webhook URL:
Replacehttps://your-domain.com/api/stripe/webhookyour-domainwith your actual Hygate domain. - Select these events:
checkout.session.completedpayment_intent.succeededcharge.refunded
- Click Add endpoint
- Copy the Signing secret (starts with
whsec_)
Step 3: Enter Credentials in Hygate
- Go to Settings → Stripe
- Paste:
- Publishable Key
- Secret Key
- Webhook Secret
- Click Test Connection
- If successful, click Save
How Payments Work
Door Payment Flow
Guest scans QR code
│
▼
/pay/[uid] — payment page loads
Shows: door name, price, duration, branding
│
▼
Guest enters email and clicks Pay
│
▼
Hygate → Stripe: createCheckoutSession()
Returns: Stripe Checkout URL
│
▼
Guest redirected to Stripe Checkout
Enters card details on Stripe's hosted page
│
▼
Guest pays → Stripe charges card
│
▼
Stripe → Hygate webhook: payment_intent.succeeded
│
▼
Hygate creates Payment record (SUCCEEDED)
Hygate → TTLock: generate QR access code
│
▼
Guest redirected to /access/[token]
Shows: QR code, access instructions
Device Payment Flow
Guest scans QR code
│
▼
/use/[uid] — usage plan selection page
Shows: device name, available plans, branding
│
▼
Guest selects a usage plan
Guest enters email and clicks Pay
│
▼
Hygate → Stripe: createPaymentIntent()
Returns: Stripe Checkout URL
│
▼
Guest redirected to Stripe Checkout
Enters card details
│
▼
Guest pays → Stripe charges card
│
▼
Stripe → Hygate webhook: payment_intent.succeeded
│
▼
Hygate creates Payment record (SUCCEEDED)
Hygate creates DeviceSession (ACTIVE)
Grace period begins → Relay turns ON
│
▼
Guest redirected to /session/[token]
Shows: countdown timer, plan details
Stripe Webhook Events
| Event | Action in Hygate |
|---|---|
checkout.session.completed | Creates Payment record (door) |
payment_intent.succeeded | Creates Payment + DeviceSession (device) |
charge.refunded | Updates Payment status to REFUNDED |
Idempotency
Hygate implements webhook idempotency to prevent duplicate charges:
- Before processing a webhook, Hygate checks if a Payment already exists for that Stripe Payment Intent
- If a duplicate is detected, the webhook is logged as
duplicate_payment_ignoredand no action is taken - This ensures multiple webhook deliveries (Stripe retries automatically) don't cause duplicate charges
Webhook Security
| Feature | Description |
|---|---|
| Signature verification | Hygate verifies the Stripe signature header on every webhook |
| Secret storage | Webhook secret is encrypted in the database |
| Replay prevention | Timestamps are validated to prevent replay attacks |
Test Payments
Stripe Test Mode
Use Stripe's test mode for development and QA:
- Toggle Test Mode in the Stripe Dashboard
- Use test API keys (prefix
pk_test_,sk_test_) - Test with Stripe's test card numbers:
| Card Number | Result |
|---|---|
4242 4242 4242 4242 | Successful payment |
4000 0000 0000 0002 | Always declined |
4000 0025 0000 3155 | Requires 3D Secure |
4000 0000 0000 9995 | Insufficient funds |
4100 0000 0000 0019 | Failed postcode |
Testing Webhooks Locally
Use the Stripe CLI to forward webhooks to your local development server:
stripe listen --forward-to localhost:3000/api/stripe/webhook
This prints a webhook secret that you can enter in Hygate's Stripe settings.
Troubleshooting
Webhook Not Delivered
| Check | Action |
|---|---|
| Webhook URL | Verify the URL is exactly https://your-domain/api/stripe/webhook |
| Endpoint enabled | Check Stripe Dashboard → Webhooks → your endpoint |
| Events selected | Confirm payment_intent.succeeded and charge.refunded are subscribed |
| Test mode | Ensure you're using the same mode (test/live) in both Stripe and Hygate |
"Connection test failed" in Hygate
| Check | Action |
|---|---|
| API keys | Verify you're using the correct keys (test vs. live) |
| Key format | Ensure no extra spaces or newlines in pasted values |
| Account status | Check that your Stripe account is active |
Payment Succeeded But No Access Code
| Check | Action |
|---|---|
| Webhook logs | Check Stripe Dashboard → Developers → Logs |
| Hygate payments | Check Payments module for the record |
| Door status | Verify the door is not in Maintenance Mode |
| TTLock connection | Check TTLock integration status |
Refund Not Showing
Refunds must be processed through the Stripe Dashboard. After initiating a refund:
- Stripe sends a
charge.refundedwebhook to Hygate - Hygate updates the Payment status to REFUNDED
- Check the Payments module — the refund should appear within seconds
Duplicate Payments
If you see duplicate payment records, check:
- Stripe webhook logs for duplicate event deliveries
- Whether the guest refreshed the payment page
- Hygate audit logs for
duplicate_payment_ignoredentries
Production Checklist
Before going live:
- Switch from test to live API keys
- Configure the production webhook URL
- Verify webhook events are subscribed (
payment_intent.succeeded,checkout.session.completed,charge.refunded) - Test a full payment flow with a real card
- Verify branding settings are correct on payment pages
- Set up Stripe Dashboard alerts for failed payments