Stripe: No signatures found matching the expected signature for payload using Django
Question:
I am getting the below error while Stripe calling the defined Web hook
No signatures found matching the expected signature for payload
I am following this article: https://stripe.com/docs/billing/subscriptions/checkout#provision-and-monitor
And I have following code:
@csrf_exempt
def saaswebhookview(request):
try:
stripe.api_key = settings.STRIPE_SECRET_KEY
webhook_secret = 'stripe_key'
request_data = request.POST
if webhook_secret:
try:
signature = request.headers.get('stripe-signature')
# signature = request.META['stripe-signature']
event = stripe.Webhook.construct_event(
payload=request.POST, sig_header=signature, secret=webhook_secret)
data = event['data']
except Exception as e:
print(str(e))
return JsonResponse({'status': 'error', 'error': str(e)})
event_type = event['type']
else:
data = request_data['data']
event_type = request_data['type']
data_object = data['object']
if event_type == 'checkout.session.completed':
print(data)
elif event_type == 'invoice.paid':
print(data)
elif event_type == 'invoice.payment_failed':
print(data)
else:
print('Unhandled event type {}'.format(event_type))
return JsonResponse({'status': 'success'}, safe=False)
except Exception as e:
return JsonResponse({'status': 'success', 'error': str(e)}, safe=False)
But strangely this throws me the error don’t know why?
Answers:
The Stripe library requires the raw body of the webhook request for signature verification to work. It looks like you’re supplying request.POST
, which is an altered version of the body.
If you use request.body
instead it should work as expected.
Script to verify your Stripe Webhook API with generated signature
import hmac
import time
from hashlib import sha256
import requests
import stripe
webhook_url = 'https://your-site/api/stripe/webhook/'
stripe_secret = 'YOUR_STRIPE_WEBHOOK_SIGN_SECRET' # whsec_..
webhook_json_file = 'PATH_TO_JSON_FILE_WITH_WH_DATA/webhook_example.json'
def generate_stripe_signature_header(payload: str):
timestamp_utc = int(time.time())
signed_payload = "%d.%s" % (timestamp_utc, payload)
v1_sig = compute_signature(signed_payload, secret=stripe_secret)
return f't={timestamp_utc},v1={v1_sig}'
def compute_signature(payload: str, secret):
"""Take from stripe"""
mac = hmac.new(
secret.encode("utf-8"),
msg=payload.encode("utf-8"),
digestmod=sha256,
)
return mac.hexdigest()
with open(webhook_json_file, 'r') as f:
payload = f.read()
signature = generate_stripe_signature_header(payload=payload)
headers = {
'STRIPE-SIGNATURE': signature,
'Content-Type': 'application/json',
}
session = requests.Session()
session.headers.update(**headers)
response = session.post(webhook_url, data=payload)
print(response.content)
print(response.status_code)
response.raise_for_status()
P.S. IF you want to use it for unit tests / Django Test Client, you need to add HTTP_ prefix to the signature header
signature = generate_stripe_signature_header(payload=payload)
headers = {
'HTTP_STRIPE-SIGNATURE': signature,
'Content-Type': 'application/json',
}
self.client.post(webhook_url, data=payload, **headers)
In my case, for to resolve my problem I decoding the body:
payload = request.body.decode('utf-8')
In my case I was using the secret from the stripe-cli.
Instead, go the the webhook section, click your webhook and finally click reveal secret
. This will show the secret you want.
The answer provided by pymen worked perfectly when trying to use it for unit tests / Django Test Client. However, I kept getting the following error: "AttributeError: ‘str’ object has no attribute ‘items’" and i fixed it by adding content_type=’application/json’ in self.client.post.
self.client.post(webhook_url, data=payload, content_type='application/json', **headers)
request_body = request.body
request_data = request.data
...
event = stripe.Webhook.construct_event(payload=request_body, sig_header=signature, secret=webhook_secret)
I am getting the below error while Stripe calling the defined Web hook
No signatures found matching the expected signature for payload
I am following this article: https://stripe.com/docs/billing/subscriptions/checkout#provision-and-monitor
And I have following code:
@csrf_exempt
def saaswebhookview(request):
try:
stripe.api_key = settings.STRIPE_SECRET_KEY
webhook_secret = 'stripe_key'
request_data = request.POST
if webhook_secret:
try:
signature = request.headers.get('stripe-signature')
# signature = request.META['stripe-signature']
event = stripe.Webhook.construct_event(
payload=request.POST, sig_header=signature, secret=webhook_secret)
data = event['data']
except Exception as e:
print(str(e))
return JsonResponse({'status': 'error', 'error': str(e)})
event_type = event['type']
else:
data = request_data['data']
event_type = request_data['type']
data_object = data['object']
if event_type == 'checkout.session.completed':
print(data)
elif event_type == 'invoice.paid':
print(data)
elif event_type == 'invoice.payment_failed':
print(data)
else:
print('Unhandled event type {}'.format(event_type))
return JsonResponse({'status': 'success'}, safe=False)
except Exception as e:
return JsonResponse({'status': 'success', 'error': str(e)}, safe=False)
But strangely this throws me the error don’t know why?
The Stripe library requires the raw body of the webhook request for signature verification to work. It looks like you’re supplying request.POST
, which is an altered version of the body.
If you use request.body
instead it should work as expected.
Script to verify your Stripe Webhook API with generated signature
import hmac
import time
from hashlib import sha256
import requests
import stripe
webhook_url = 'https://your-site/api/stripe/webhook/'
stripe_secret = 'YOUR_STRIPE_WEBHOOK_SIGN_SECRET' # whsec_..
webhook_json_file = 'PATH_TO_JSON_FILE_WITH_WH_DATA/webhook_example.json'
def generate_stripe_signature_header(payload: str):
timestamp_utc = int(time.time())
signed_payload = "%d.%s" % (timestamp_utc, payload)
v1_sig = compute_signature(signed_payload, secret=stripe_secret)
return f't={timestamp_utc},v1={v1_sig}'
def compute_signature(payload: str, secret):
"""Take from stripe"""
mac = hmac.new(
secret.encode("utf-8"),
msg=payload.encode("utf-8"),
digestmod=sha256,
)
return mac.hexdigest()
with open(webhook_json_file, 'r') as f:
payload = f.read()
signature = generate_stripe_signature_header(payload=payload)
headers = {
'STRIPE-SIGNATURE': signature,
'Content-Type': 'application/json',
}
session = requests.Session()
session.headers.update(**headers)
response = session.post(webhook_url, data=payload)
print(response.content)
print(response.status_code)
response.raise_for_status()
P.S. IF you want to use it for unit tests / Django Test Client, you need to add HTTP_ prefix to the signature header
signature = generate_stripe_signature_header(payload=payload)
headers = {
'HTTP_STRIPE-SIGNATURE': signature,
'Content-Type': 'application/json',
}
self.client.post(webhook_url, data=payload, **headers)
In my case, for to resolve my problem I decoding the body:
payload = request.body.decode('utf-8')
In my case I was using the secret from the stripe-cli.
Instead, go the the webhook section, click your webhook and finally click reveal secret
. This will show the secret you want.
The answer provided by pymen worked perfectly when trying to use it for unit tests / Django Test Client. However, I kept getting the following error: "AttributeError: ‘str’ object has no attribute ‘items’" and i fixed it by adding content_type=’application/json’ in self.client.post.
self.client.post(webhook_url, data=payload, content_type='application/json', **headers)
request_body = request.body
request_data = request.data
...
event = stripe.Webhook.construct_event(payload=request_body, sig_header=signature, secret=webhook_secret)