Stripe Webhook Specs, Events, and Examples
Stripe uses webhooks to notify third-party apps of events such as payments processed, refund requested, product (skus) created, and charges expired. Stripe events can be used to trigger action in other systems such as changing user access in your systems after a payment is processed.
Stripe offers two payload formats: snapshot events that include the full API object, and thin events that include only the related object's ID, so your code fetches the latest state from the API. Thin events are an example of dataless notifications.
Specifications |
|
Supported Events | |
Security Headers |
|
Documentation | |
SDKs and Sample Code | Stripe provide SDKs with an interactive webhook builder for the following programming languages:
|
Sample Validation
Stripe recommends its SDKs for verification. If you verify manually:
const crypto = require('crypto')
const express = require('express')
const app = express()
const sigHeader = 'Stripe-Signature'
const hashAlgo = 'sha256'
const encode = 'hex'
const hmacSecret = process.env.WEBHOOK_SECRET
// Use the raw body: re-serialized JSON will not match the signature
app.post('/stripe-webhook', express.raw({ type: 'application/json' }), (req, res) => {
//01: Get timestamp and every v1 signature (there may be several during rotation)
const elements = (req.get(sigHeader) || '').split(',').map(e => e.split('='))
const timestamp = (elements.find(([k]) => k === 't') || [])[1]
const signatures = elements.filter(([k]) => k === 'v1').map(([, v]) => v)
//02: Validate replay prevention with 5 minute timeframe
if (!timestamp || Math.abs(Date.now() - timestamp * 1000) > 5 * 60 * 1000) {
return res.status(403).send('Request expired')
}
//03: Validate signature against each v1 value
const message = `${timestamp}.${req.body.toString('utf8')}`
const digest = Buffer.from(crypto.createHmac(hashAlgo, hmacSecret).update(message).digest(encode), 'utf8')
const valid = signatures.some(sig => {
const providerSig = Buffer.from(sig, 'utf8')
return providerSig.length === digest.length && crypto.timingSafeEqual(digest, providerSig)
})
if (!valid) {
return res.status(401).send('Request unauthorized')
}
//04: Process message (dedupe on event id)
res.json({ message: "Success" })
})