Payments

Webhooks

How Keyrail receives, verifies and processes payment provider webhooks, and what it answers.

Keyrail receives webhooks from your payment provider and turns them into license changes. You don’t write any webhook code: you paste one URL into your provider.

Endpoint#

text
POST https://api.keyrail.tech/v1/webhooks/{provider}/{integrationId}

provider is paddle or lemonsqueezy; integrationId is the int_… ID of your integration. The full URL is returned as webhookUrl when you create the integration.

Signature verification#

Every request is verified with the integration’s webhookSecret before anything else. An invalid or missing signature gets 401 and is not processed.

ProviderHeaderCheck
Paddle BillingPaddle-Signature: ts=…;h1=…h1 = HMAC-SHA256 of ts:rawBody; ts within 5 minutes.
Lemon SqueezyX-SignatureHex HMAC-SHA256 of the raw body.

Responses#

SituationResponseProvider behavior
Processed200Done.
Event already processed (same provider event ID)200 { "result": "duplicate" }Done. Retries are safe.
Price or variant not in priceMap200 { "result": "ignored" }, and an event in the audit logDone. Map the price, then replay the event from your provider if needed.
Bad signature, or no secret saved yet401Check the secret.
No integration with this ID (typo, or disconnected)404Copy the Webhook URL again.
Hourly budget of license-key emails of your Keyrail plan used up429The provider retries later.
Another key e-mail for the same license is in progress409Retry later; this event has not been marked processed.
Processing error500The provider retries later.

Processing rules#

  • Customer: matched by email within your account, or created. Paddle events carry a customer_id instead of an email: Keyrail maps it from customer.created / customer.updated, or fetches it with the integration’s optional Paddle API key. Until the customer is known, a Paddle transaction gets 500 and is retried by Paddle.
  • Plan: looked up in the integration’s priceMap. A mapped subscription payment or update changes an existing license's plan in place: its key and activated machines stay the same. New entitlements, the default activation limit and the new plan's major-version limit apply at the next certificate refresh; an explicit per-license activation override is kept. A status update without a mapped price keeps the current plan.
  • One license per customer and plan subscription: re-subscribing after expiry reuses the customer’s license (and key) for the same plan.
  • New license: the key is emailed to the customer, branded with your product name and sender name.
  • Ordering: subscription status events older than the license’s last processed event are ignored, including concurrent deliveries and events within the same second. An older payment may extend the paid period but cannot restore an older plan, quantity or status. Revocation is final, even when a payment arrives at the same time.
  • Concurrent e-mails: only one key send per license runs at a time. Failed sends release their reservation; an abandoned reservation expires after one minute. A provider timeout after it accepted a message can still leave delivery uncertain, so this is not an exactly-once delivery guarantee.

The event-by-event mapping is in each provider guide: Paddle Billing, Lemon Squeezy.

Debugging#

Every webhook received appears in Events and in the History of its license in the dashboard, and through GET /v1/events?license=lic_…. Ignored events (unknown price) are logged too, so a missing mapping is easy to spot. Common cases: troubleshooting.

View as Markdown

Need help? See Troubleshooting or write to support@keyrail.tech.