We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Djstripe is suppressing backtraces that would otherwise be shown by the logging framework
Example logging config:

```
LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'verbose': {
            'format': '%(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s'
        },
    },
    'handlers': {
        'file': {
            'level': 'ERROR',
            'class': 'logging.FileHandler',
            'filename': os.path.join(BASE_DIR, 'logs/django.log'),
            'formatter': 'verbose',
        },
        'mail_admins': {
            'level': 'ERROR',
            'class': 'django.utils.log.AdminEmailHandler',
            'formatter': 'verbose',
        },
    },
    'loggers': {
        'django': {
            'handlers': ['file', 'mail_admins'],
            'level': 'ERROR',
            'propagate': True,
        },
    },
}
```

Normally it would print an error and a backtrace, but it can't because djstripe is explicitly returning a 500 rather than raising the exception again!

I initially suggested that return line in https://github.com/dj-stripe/dj-stripe/blob/master/djstripe/views.py#L39 should be replaced with `raise trigger.exception`. Though I now realised that it's actually just a string as it's being processed in https://github.com/dj-stripe/dj-stripe/blob/master/djstripe/models/webhooks.py#L101
The correct solution would actually to remove the "return trigger.exception" from views.py, and reraise the exception in the code block I linked to in L101. Serializing it is fine, but also raise it again. Let django handle it.

Thoughts?

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `djstripe/views.py`
Content:
```
1 """
2 dj-stripe - Views related to the djstripe app.
3 """
4 import logging
5 
6 from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseServerError
7 from django.utils.decorators import method_decorator
8 from django.views.decorators.csrf import csrf_exempt
9 from django.views.generic import View
10 
11 from .models import WebhookEventTrigger
12 
13 logger = logging.getLogger(__name__)
14 
15 
16 @method_decorator(csrf_exempt, name="dispatch")
17 class ProcessWebhookView(View):
18 	"""
19 	A Stripe Webhook handler view.
20 
21 	This will create a WebhookEventTrigger instance, verify it,
22 	then attempt to process it.
23 
24 	If the webhook cannot be verified, returns HTTP 400.
25 
26 	If an exception happens during processing, returns HTTP 500.
27 	"""
28 
29 	def post(self, request):
30 		if "HTTP_STRIPE_SIGNATURE" not in request.META:
31 			# Do not even attempt to process/store the event if there is
32 			# no signature in the headers so we avoid overfilling the db.
33 			return HttpResponseBadRequest()
34 
35 		trigger = WebhookEventTrigger.from_request(request)
36 
37 		if trigger.exception:
38 			# An exception happened, return 500
39 			return HttpResponseServerError()
40 
41 		if trigger.is_test_event:
42 			# Since we don't do signature verification, we have to skip trigger.valid
43 			return HttpResponse("Test webhook successfully received!")
44 
45 		if not trigger.valid:
46 			# Webhook Event did not validate, return 400
47 			return HttpResponseBadRequest()
48 
49 		return HttpResponse(str(trigger.id))
50
```

Path: `djstripe/models/webhooks.py`
Content:
```
1 import json
2 import warnings
3 from traceback import format_exc
4 
5 import stripe
6 from django.db import models
7 from django.utils.functional import cached_property
8 
9 from .. import settings as djstripe_settings
10 from .. import webhooks
11 from ..context_managers import stripe_temporary_api_version
12 from ..fields import JSONField
13 from ..signals import webhook_processing_error
14 from ..utils import fix_django_headers
15 from .base import logger
16 from .core import Event
17 
18 
19 def _get_version():
20 	from .. import __version__
21 
22 	return __version__
23 
24 
25 class WebhookEventTrigger(models.Model):
26 	"""
27 	An instance of a request that reached the server endpoint for Stripe webhooks.
28 
29 	Webhook Events are initially **UNTRUSTED**, as it is possible for any web entity to
30 	post any data to our webhook url. Data posted may be valid Stripe information,  garbage, or even malicious.
31 	The 'valid' flag in this model monitors this.
32 	"""
33 
34 	id = models.BigAutoField(primary_key=True)
35 	remote_ip = models.GenericIPAddressField(help_text="IP address of the request client.")
36 	headers = JSONField()
37 	body = models.TextField(blank=True)
38 	valid = models.BooleanField(
39 		default=False, help_text="Whether or not the webhook event has passed validation"
40 	)
41 	processed = models.BooleanField(
42 		default=False,
43 		help_text="Whether or not the webhook event has been successfully processed",
44 	)
45 	exception = models.CharField(max_length=128, blank=True)
46 	traceback = models.TextField(
47 		blank=True, help_text="Traceback if an exception was thrown during processing"
48 	)
49 	event = models.ForeignKey(
50 		"Event",
51 		on_delete=models.SET_NULL,
52 		null=True,
53 		blank=True,
54 		help_text="Event object contained in the (valid) Webhook",
55 	)
56 	djstripe_version = models.CharField(
57 		max_length=32,
58 		default=_get_version,  # Needs to be a callable, otherwise it's a db default.
59 		help_text="The version of dj-stripe when the webhook was received",
60 	)
61 	created = models.DateTimeField(auto_now_add=True)
62 	updated = models.DateTimeField(auto_now=True)
63 
64 	@classmethod
65 	def from_request(cls, request):
66 		"""
67 		Create, validate and process a WebhookEventTrigger given a Django
68 		request object.
69 
70 		The process is three-fold:
71 		1. Create a WebhookEventTrigger object from a Django request.
72 		2. Validate the WebhookEventTrigger as a Stripe event using the API.
73 		3. If valid, process it into an Event object (and child resource).
74 		"""
75 
76 		headers = fix_django_headers(request.META)
77 		assert headers
78 		try:
79 			body = request.body.decode(request.encoding or "utf-8")
80 		except Exception:
81 			body = "(error decoding body)"
82 
83 		ip = request.META.get("REMOTE_ADDR")
84 		if ip is None:
85 			warnings.warn(
86 				"Could not determine remote IP (missing REMOTE_ADDR). "
87 				"This is likely an issue with your wsgi/server setup."
88 			)
89 			ip = "0.0.0.0"
90 		obj = cls.objects.create(headers=headers, body=body, remote_ip=ip)
91 
92 		try:
93 			obj.valid = obj.validate()
94 			if obj.valid:
95 				if djstripe_settings.WEBHOOK_EVENT_CALLBACK:
96 					# If WEBHOOK_EVENT_CALLBACK, pass it for processing
97 					djstripe_settings.WEBHOOK_EVENT_CALLBACK(obj)
98 				else:
99 					# Process the item (do not save it, it'll get saved below)
100 					obj.process(save=False)
101 		except Exception as e:
102 			max_length = WebhookEventTrigger._meta.get_field("exception").max_length
103 			obj.exception = str(e)[:max_length]
104 			obj.traceback = format_exc()
105 
106 			# Send the exception as the webhook_processing_error signal
107 			webhook_processing_error.send(
108 				sender=WebhookEventTrigger, exception=e, data=getattr(e, "http_body", "")
109 			)
110 		finally:
111 			obj.save()
112 
113 		return obj
114 
115 	@cached_property
116 	def json_body(self):
117 		try:
118 			return json.loads(self.body)
119 		except ValueError:
120 			return {}
121 
122 	@property
123 	def is_test_event(self):
124 		return self.json_body.get("id") == webhooks.TEST_EVENT_ID
125 
126 	def validate(self, api_key=None):
127 		"""
128 		The original contents of the Event message must be confirmed by
129 		refetching it and comparing the fetched data with the original data.
130 
131 		This function makes an API call to Stripe to redownload the Event data
132 		and returns whether or not it matches the WebhookEventTrigger data.
133 		"""
134 
135 		local_data = self.json_body
136 		if "id" not in local_data or "livemode" not in local_data:
137 			return False
138 
139 		if self.is_test_event:
140 			logger.info("Test webhook received: {}".format(local_data))
141 			return False
142 
143 		if djstripe_settings.WEBHOOK_VALIDATION is None:
144 			# validation disabled
145 			return True
146 		elif (
147 			djstripe_settings.WEBHOOK_VALIDATION == "verify_signature"
148 			and djstripe_settings.WEBHOOK_SECRET
149 		):
150 			try:
151 				stripe.WebhookSignature.verify_header(
152 					self.body,
153 					self.headers.get("stripe-signature"),
154 					djstripe_settings.WEBHOOK_SECRET,
155 					djstripe_settings.WEBHOOK_TOLERANCE,
156 				)
157 			except stripe.error.SignatureVerificationError:
158 				return False
159 			else:
160 				return True
161 
162 		livemode = local_data["livemode"]
163 		api_key = api_key or djstripe_settings.get_default_api_key(livemode)
164 
165 		# Retrieve the event using the api_version specified in itself
166 		with stripe_temporary_api_version(local_data["api_version"], validate=False):
167 			remote_data = Event.stripe_class.retrieve(id=local_data["id"], api_key=api_key)
168 
169 		return local_data["data"] == remote_data["data"]
170 
171 	def process(self, save=True):
172 		# Reset traceback and exception in case of reprocessing
173 		self.exception = ""
174 		self.traceback = ""
175 
176 		self.event = Event.process(self.json_body)
177 		self.processed = True
178 		if save:
179 			self.save()
180 
181 		return self.event
182
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/djstripe/models/webhooks.py b/djstripe/models/webhooks.py
--- a/djstripe/models/webhooks.py
+++ b/djstripe/models/webhooks.py
@@ -107,6 +107,9 @@
 			webhook_processing_error.send(
 				sender=WebhookEventTrigger, exception=e, data=getattr(e, "http_body", "")
 			)
+
+			# re-raise the exception so Django sees it
+			raise e
 		finally:
 			obj.save()
 
diff --git a/djstripe/views.py b/djstripe/views.py
--- a/djstripe/views.py
+++ b/djstripe/views.py
@@ -3,7 +3,7 @@
 """
 import logging
 
-from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseServerError
+from django.http import HttpResponse, HttpResponseBadRequest
 from django.utils.decorators import method_decorator
 from django.views.decorators.csrf import csrf_exempt
 from django.views.generic import View
@@ -34,10 +34,6 @@
 
 		trigger = WebhookEventTrigger.from_request(request)
 
-		if trigger.exception:
-			# An exception happened, return 500
-			return HttpResponseServerError()
-
 		if trigger.is_test_event:
 			# Since we don't do signature verification, we have to skip trigger.valid
 			return HttpResponse("Test webhook successfully received!")

{"golden_diff": "diff --git a/djstripe/models/webhooks.py b/djstripe/models/webhooks.py\n--- a/djstripe/models/webhooks.py\n+++ b/djstripe/models/webhooks.py\n@@ -107,6 +107,9 @@\n \t\t\twebhook_processing_error.send(\n \t\t\t\tsender=WebhookEventTrigger, exception=e, data=getattr(e, \"http_body\", \"\")\n \t\t\t)\n+\n+\t\t\t# re-raise the exception so Django sees it\n+\t\t\traise e\n \t\tfinally:\n \t\t\tobj.save()\n \ndiff --git a/djstripe/views.py b/djstripe/views.py\n--- a/djstripe/views.py\n+++ b/djstripe/views.py\n@@ -3,7 +3,7 @@\n \"\"\"\n import logging\n \n-from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseServerError\n+from django.http import HttpResponse, HttpResponseBadRequest\n from django.utils.decorators import method_decorator\n from django.views.decorators.csrf import csrf_exempt\n from django.views.generic import View\n@@ -34,10 +34,6 @@\n \n \t\ttrigger = WebhookEventTrigger.from_request(request)\n \n-\t\tif trigger.exception:\n-\t\t\t# An exception happened, return 500\n-\t\t\treturn HttpResponseServerError()\n-\n \t\tif trigger.is_test_event:\n \t\t\t# Since we don't do signature verification, we have to skip trigger.valid\n \t\t\treturn HttpResponse(\"Test webhook successfully received!\")\n", "issue": "Djstripe is suppressing backtraces that would otherwise be shown by the logging framework\nExample logging config:\r\n\r\n```\r\nLOGGING = {\r\n    'version': 1,\r\n    'disable_existing_loggers': False,\r\n    'formatters': {\r\n        'verbose': {\r\n            'format': '%(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s'\r\n        },\r\n    },\r\n    'handlers': {\r\n        'file': {\r\n            'level': 'ERROR',\r\n            'class': 'logging.FileHandler',\r\n            'filename': os.path.join(BASE_DIR, 'logs/django.log'),\r\n            'formatter': 'verbose',\r\n        },\r\n        'mail_admins': {\r\n            'level': 'ERROR',\r\n            'class': 'django.utils.log.AdminEmailHandler',\r\n            'formatter': 'verbose',\r\n        },\r\n    },\r\n    'loggers': {\r\n        'django': {\r\n            'handlers': ['file', 'mail_admins'],\r\n            'level': 'ERROR',\r\n            'propagate': True,\r\n        },\r\n    },\r\n}\r\n```\r\n\r\nNormally it would print an error and a backtrace, but it can't because djstripe is explicitly returning a 500 rather than raising the exception again!\r\n\r\nI initially suggested that return line in https://github.com/dj-stripe/dj-stripe/blob/master/djstripe/views.py#L39 should be replaced with `raise trigger.exception`. Though I now realised that it's actually just a string as it's being processed in https://github.com/dj-stripe/dj-stripe/blob/master/djstripe/models/webhooks.py#L101\r\nThe correct solution would actually to remove the \"return trigger.exception\" from views.py, and reraise the exception in the code block I linked to in L101. Serializing it is fine, but also raise it again. Let django handle it.\r\n\r\nThoughts?\n", "before_files": [{"content": "\"\"\"\ndj-stripe - Views related to the djstripe app.\n\"\"\"\nimport logging\n\nfrom django.http import HttpResponse, HttpResponseBadRequest, HttpResponseServerError\nfrom django.utils.decorators import method_decorator\nfrom django.views.decorators.csrf import csrf_exempt\nfrom django.views.generic import View\n\nfrom .models import WebhookEventTrigger\n\nlogger = logging.getLogger(__name__)\n\n\n@method_decorator(csrf_exempt, name=\"dispatch\")\nclass ProcessWebhookView(View):\n\t\"\"\"\n\tA Stripe Webhook handler view.\n\n\tThis will create a WebhookEventTrigger instance, verify it,\n\tthen attempt to process it.\n\n\tIf the webhook cannot be verified, returns HTTP 400.\n\n\tIf an exception happens during processing, returns HTTP 500.\n\t\"\"\"\n\n\tdef post(self, request):\n\t\tif \"HTTP_STRIPE_SIGNATURE\" not in request.META:\n\t\t\t# Do not even attempt to process/store the event if there is\n\t\t\t# no signature in the headers so we avoid overfilling the db.\n\t\t\treturn HttpResponseBadRequest()\n\n\t\ttrigger = WebhookEventTrigger.from_request(request)\n\n\t\tif trigger.exception:\n\t\t\t# An exception happened, return 500\n\t\t\treturn HttpResponseServerError()\n\n\t\tif trigger.is_test_event:\n\t\t\t# Since we don't do signature verification, we have to skip trigger.valid\n\t\t\treturn HttpResponse(\"Test webhook successfully received!\")\n\n\t\tif not trigger.valid:\n\t\t\t# Webhook Event did not validate, return 400\n\t\t\treturn HttpResponseBadRequest()\n\n\t\treturn HttpResponse(str(trigger.id))\n", "path": "djstripe/views.py"}, {"content": "import json\nimport warnings\nfrom traceback import format_exc\n\nimport stripe\nfrom django.db import models\nfrom django.utils.functional import cached_property\n\nfrom .. import settings as djstripe_settings\nfrom .. import webhooks\nfrom ..context_managers import stripe_temporary_api_version\nfrom ..fields import JSONField\nfrom ..signals import webhook_processing_error\nfrom ..utils import fix_django_headers\nfrom .base import logger\nfrom .core import Event\n\n\ndef _get_version():\n\tfrom .. import __version__\n\n\treturn __version__\n\n\nclass WebhookEventTrigger(models.Model):\n\t\"\"\"\n\tAn instance of a request that reached the server endpoint for Stripe webhooks.\n\n\tWebhook Events are initially **UNTRUSTED**, as it is possible for any web entity to\n\tpost any data to our webhook url. Data posted may be valid Stripe information,  garbage, or even malicious.\n\tThe 'valid' flag in this model monitors this.\n\t\"\"\"\n\n\tid = models.BigAutoField(primary_key=True)\n\tremote_ip = models.GenericIPAddressField(help_text=\"IP address of the request client.\")\n\theaders = JSONField()\n\tbody = models.TextField(blank=True)\n\tvalid = models.BooleanField(\n\t\tdefault=False, help_text=\"Whether or not the webhook event has passed validation\"\n\t)\n\tprocessed = models.BooleanField(\n\t\tdefault=False,\n\t\thelp_text=\"Whether or not the webhook event has been successfully processed\",\n\t)\n\texception = models.CharField(max_length=128, blank=True)\n\ttraceback = models.TextField(\n\t\tblank=True, help_text=\"Traceback if an exception was thrown during processing\"\n\t)\n\tevent = models.ForeignKey(\n\t\t\"Event\",\n\t\ton_delete=models.SET_NULL,\n\t\tnull=True,\n\t\tblank=True,\n\t\thelp_text=\"Event object contained in the (valid) Webhook\",\n\t)\n\tdjstripe_version = models.CharField(\n\t\tmax_length=32,\n\t\tdefault=_get_version,  # Needs to be a callable, otherwise it's a db default.\n\t\thelp_text=\"The version of dj-stripe when the webhook was received\",\n\t)\n\tcreated = models.DateTimeField(auto_now_add=True)\n\tupdated = models.DateTimeField(auto_now=True)\n\n\t@classmethod\n\tdef from_request(cls, request):\n\t\t\"\"\"\n\t\tCreate, validate and process a WebhookEventTrigger given a Django\n\t\trequest object.\n\n\t\tThe process is three-fold:\n\t\t1. Create a WebhookEventTrigger object from a Django request.\n\t\t2. Validate the WebhookEventTrigger as a Stripe event using the API.\n\t\t3. If valid, process it into an Event object (and child resource).\n\t\t\"\"\"\n\n\t\theaders = fix_django_headers(request.META)\n\t\tassert headers\n\t\ttry:\n\t\t\tbody = request.body.decode(request.encoding or \"utf-8\")\n\t\texcept Exception:\n\t\t\tbody = \"(error decoding body)\"\n\n\t\tip = request.META.get(\"REMOTE_ADDR\")\n\t\tif ip is None:\n\t\t\twarnings.warn(\n\t\t\t\t\"Could not determine remote IP (missing REMOTE_ADDR). \"\n\t\t\t\t\"This is likely an issue with your wsgi/server setup.\"\n\t\t\t)\n\t\t\tip = \"0.0.0.0\"\n\t\tobj = cls.objects.create(headers=headers, body=body, remote_ip=ip)\n\n\t\ttry:\n\t\t\tobj.valid = obj.validate()\n\t\t\tif obj.valid:\n\t\t\t\tif djstripe_settings.WEBHOOK_EVENT_CALLBACK:\n\t\t\t\t\t# If WEBHOOK_EVENT_CALLBACK, pass it for processing\n\t\t\t\t\tdjstripe_settings.WEBHOOK_EVENT_CALLBACK(obj)\n\t\t\t\telse:\n\t\t\t\t\t# Process the item (do not save it, it'll get saved below)\n\t\t\t\t\tobj.process(save=False)\n\t\texcept Exception as e:\n\t\t\tmax_length = WebhookEventTrigger._meta.get_field(\"exception\").max_length\n\t\t\tobj.exception = str(e)[:max_length]\n\t\t\tobj.traceback = format_exc()\n\n\t\t\t# Send the exception as the webhook_processing_error signal\n\t\t\twebhook_processing_error.send(\n\t\t\t\tsender=WebhookEventTrigger, exception=e, data=getattr(e, \"http_body\", \"\")\n\t\t\t)\n\t\tfinally:\n\t\t\tobj.save()\n\n\t\treturn obj\n\n\t@cached_property\n\tdef json_body(self):\n\t\ttry:\n\t\t\treturn json.loads(self.body)\n\t\texcept ValueError:\n\t\t\treturn {}\n\n\t@property\n\tdef is_test_event(self):\n\t\treturn self.json_body.get(\"id\") == webhooks.TEST_EVENT_ID\n\n\tdef validate(self, api_key=None):\n\t\t\"\"\"\n\t\tThe original contents of the Event message must be confirmed by\n\t\trefetching it and comparing the fetched data with the original data.\n\n\t\tThis function makes an API call to Stripe to redownload the Event data\n\t\tand returns whether or not it matches the WebhookEventTrigger data.\n\t\t\"\"\"\n\n\t\tlocal_data = self.json_body\n\t\tif \"id\" not in local_data or \"livemode\" not in local_data:\n\t\t\treturn False\n\n\t\tif self.is_test_event:\n\t\t\tlogger.info(\"Test webhook received: {}\".format(local_data))\n\t\t\treturn False\n\n\t\tif djstripe_settings.WEBHOOK_VALIDATION is None:\n\t\t\t# validation disabled\n\t\t\treturn True\n\t\telif (\n\t\t\tdjstripe_settings.WEBHOOK_VALIDATION == \"verify_signature\"\n\t\t\tand djstripe_settings.WEBHOOK_SECRET\n\t\t):\n\t\t\ttry:\n\t\t\t\tstripe.WebhookSignature.verify_header(\n\t\t\t\t\tself.body,\n\t\t\t\t\tself.headers.get(\"stripe-signature\"),\n\t\t\t\t\tdjstripe_settings.WEBHOOK_SECRET,\n\t\t\t\t\tdjstripe_settings.WEBHOOK_TOLERANCE,\n\t\t\t\t)\n\t\t\texcept stripe.error.SignatureVerificationError:\n\t\t\t\treturn False\n\t\t\telse:\n\t\t\t\treturn True\n\n\t\tlivemode = local_data[\"livemode\"]\n\t\tapi_key = api_key or djstripe_settings.get_default_api_key(livemode)\n\n\t\t# Retrieve the event using the api_version specified in itself\n\t\twith stripe_temporary_api_version(local_data[\"api_version\"], validate=False):\n\t\t\tremote_data = Event.stripe_class.retrieve(id=local_data[\"id\"], api_key=api_key)\n\n\t\treturn local_data[\"data\"] == remote_data[\"data\"]\n\n\tdef process(self, save=True):\n\t\t# Reset traceback and exception in case of reprocessing\n\t\tself.exception = \"\"\n\t\tself.traceback = \"\"\n\n\t\tself.event = Event.process(self.json_body)\n\t\tself.processed = True\n\t\tif save:\n\t\t\tself.save()\n\n\t\treturn self.event\n", "path": "djstripe/models/webhooks.py"}], "after_files": [{"content": "\"\"\"\ndj-stripe - Views related to the djstripe app.\n\"\"\"\nimport logging\n\nfrom django.http import HttpResponse, HttpResponseBadRequest\nfrom django.utils.decorators import method_decorator\nfrom django.views.decorators.csrf import csrf_exempt\nfrom django.views.generic import View\n\nfrom .models import WebhookEventTrigger\n\nlogger = logging.getLogger(__name__)\n\n\n@method_decorator(csrf_exempt, name=\"dispatch\")\nclass ProcessWebhookView(View):\n\t\"\"\"\n\tA Stripe Webhook handler view.\n\n\tThis will create a WebhookEventTrigger instance, verify it,\n\tthen attempt to process it.\n\n\tIf the webhook cannot be verified, returns HTTP 400.\n\n\tIf an exception happens during processing, returns HTTP 500.\n\t\"\"\"\n\n\tdef post(self, request):\n\t\tif \"HTTP_STRIPE_SIGNATURE\" not in request.META:\n\t\t\t# Do not even attempt to process/store the event if there is\n\t\t\t# no signature in the headers so we avoid overfilling the db.\n\t\t\treturn HttpResponseBadRequest()\n\n\t\ttrigger = WebhookEventTrigger.from_request(request)\n\n\t\tif trigger.is_test_event:\n\t\t\t# Since we don't do signature verification, we have to skip trigger.valid\n\t\t\treturn HttpResponse(\"Test webhook successfully received!\")\n\n\t\tif not trigger.valid:\n\t\t\t# Webhook Event did not validate, return 400\n\t\t\treturn HttpResponseBadRequest()\n\n\t\treturn HttpResponse(str(trigger.id))\n", "path": "djstripe/views.py"}, {"content": "import json\nimport warnings\nfrom traceback import format_exc\n\nimport stripe\nfrom django.db import models\nfrom django.utils.functional import cached_property\n\nfrom .. import settings as djstripe_settings\nfrom .. import webhooks\nfrom ..context_managers import stripe_temporary_api_version\nfrom ..fields import JSONField\nfrom ..signals import webhook_processing_error\nfrom ..utils import fix_django_headers\nfrom .base import logger\nfrom .core import Event\n\n\ndef _get_version():\n\tfrom .. import __version__\n\n\treturn __version__\n\n\nclass WebhookEventTrigger(models.Model):\n\t\"\"\"\n\tAn instance of a request that reached the server endpoint for Stripe webhooks.\n\n\tWebhook Events are initially **UNTRUSTED**, as it is possible for any web entity to\n\tpost any data to our webhook url. Data posted may be valid Stripe information,  garbage, or even malicious.\n\tThe 'valid' flag in this model monitors this.\n\t\"\"\"\n\n\tid = models.BigAutoField(primary_key=True)\n\tremote_ip = models.GenericIPAddressField(help_text=\"IP address of the request client.\")\n\theaders = JSONField()\n\tbody = models.TextField(blank=True)\n\tvalid = models.BooleanField(\n\t\tdefault=False, help_text=\"Whether or not the webhook event has passed validation\"\n\t)\n\tprocessed = models.BooleanField(\n\t\tdefault=False,\n\t\thelp_text=\"Whether or not the webhook event has been successfully processed\",\n\t)\n\texception = models.CharField(max_length=128, blank=True)\n\ttraceback = models.TextField(\n\t\tblank=True, help_text=\"Traceback if an exception was thrown during processing\"\n\t)\n\tevent = models.ForeignKey(\n\t\t\"Event\",\n\t\ton_delete=models.SET_NULL,\n\t\tnull=True,\n\t\tblank=True,\n\t\thelp_text=\"Event object contained in the (valid) Webhook\",\n\t)\n\tdjstripe_version = models.CharField(\n\t\tmax_length=32,\n\t\tdefault=_get_version,  # Needs to be a callable, otherwise it's a db default.\n\t\thelp_text=\"The version of dj-stripe when the webhook was received\",\n\t)\n\tcreated = models.DateTimeField(auto_now_add=True)\n\tupdated = models.DateTimeField(auto_now=True)\n\n\t@classmethod\n\tdef from_request(cls, request):\n\t\t\"\"\"\n\t\tCreate, validate and process a WebhookEventTrigger given a Django\n\t\trequest object.\n\n\t\tThe process is three-fold:\n\t\t1. Create a WebhookEventTrigger object from a Django request.\n\t\t2. Validate the WebhookEventTrigger as a Stripe event using the API.\n\t\t3. If valid, process it into an Event object (and child resource).\n\t\t\"\"\"\n\n\t\theaders = fix_django_headers(request.META)\n\t\tassert headers\n\t\ttry:\n\t\t\tbody = request.body.decode(request.encoding or \"utf-8\")\n\t\texcept Exception:\n\t\t\tbody = \"(error decoding body)\"\n\n\t\tip = request.META.get(\"REMOTE_ADDR\")\n\t\tif ip is None:\n\t\t\twarnings.warn(\n\t\t\t\t\"Could not determine remote IP (missing REMOTE_ADDR). \"\n\t\t\t\t\"This is likely an issue with your wsgi/server setup.\"\n\t\t\t)\n\t\t\tip = \"0.0.0.0\"\n\t\tobj = cls.objects.create(headers=headers, body=body, remote_ip=ip)\n\n\t\ttry:\n\t\t\tobj.valid = obj.validate()\n\t\t\tif obj.valid:\n\t\t\t\tif djstripe_settings.WEBHOOK_EVENT_CALLBACK:\n\t\t\t\t\t# If WEBHOOK_EVENT_CALLBACK, pass it for processing\n\t\t\t\t\tdjstripe_settings.WEBHOOK_EVENT_CALLBACK(obj)\n\t\t\t\telse:\n\t\t\t\t\t# Process the item (do not save it, it'll get saved below)\n\t\t\t\t\tobj.process(save=False)\n\t\texcept Exception as e:\n\t\t\tmax_length = WebhookEventTrigger._meta.get_field(\"exception\").max_length\n\t\t\tobj.exception = str(e)[:max_length]\n\t\t\tobj.traceback = format_exc()\n\n\t\t\t# Send the exception as the webhook_processing_error signal\n\t\t\twebhook_processing_error.send(\n\t\t\t\tsender=WebhookEventTrigger, exception=e, data=getattr(e, \"http_body\", \"\")\n\t\t\t)\n\n\t\t\t# re-raise the exception so Django sees it\n\t\t\traise e\n\t\tfinally:\n\t\t\tobj.save()\n\n\t\treturn obj\n\n\t@cached_property\n\tdef json_body(self):\n\t\ttry:\n\t\t\treturn json.loads(self.body)\n\t\texcept ValueError:\n\t\t\treturn {}\n\n\t@property\n\tdef is_test_event(self):\n\t\treturn self.json_body.get(\"id\") == webhooks.TEST_EVENT_ID\n\n\tdef validate(self, api_key=None):\n\t\t\"\"\"\n\t\tThe original contents of the Event message must be confirmed by\n\t\trefetching it and comparing the fetched data with the original data.\n\n\t\tThis function makes an API call to Stripe to redownload the Event data\n\t\tand returns whether or not it matches the WebhookEventTrigger data.\n\t\t\"\"\"\n\n\t\tlocal_data = self.json_body\n\t\tif \"id\" not in local_data or \"livemode\" not in local_data:\n\t\t\treturn False\n\n\t\tif self.is_test_event:\n\t\t\tlogger.info(\"Test webhook received: {}\".format(local_data))\n\t\t\treturn False\n\n\t\tif djstripe_settings.WEBHOOK_VALIDATION is None:\n\t\t\t# validation disabled\n\t\t\treturn True\n\t\telif (\n\t\t\tdjstripe_settings.WEBHOOK_VALIDATION == \"verify_signature\"\n\t\t\tand djstripe_settings.WEBHOOK_SECRET\n\t\t):\n\t\t\ttry:\n\t\t\t\tstripe.WebhookSignature.verify_header(\n\t\t\t\t\tself.body,\n\t\t\t\t\tself.headers.get(\"stripe-signature\"),\n\t\t\t\t\tdjstripe_settings.WEBHOOK_SECRET,\n\t\t\t\t\tdjstripe_settings.WEBHOOK_TOLERANCE,\n\t\t\t\t)\n\t\t\texcept stripe.error.SignatureVerificationError:\n\t\t\t\treturn False\n\t\t\telse:\n\t\t\t\treturn True\n\n\t\tlivemode = local_data[\"livemode\"]\n\t\tapi_key = api_key or djstripe_settings.get_default_api_key(livemode)\n\n\t\t# Retrieve the event using the api_version specified in itself\n\t\twith stripe_temporary_api_version(local_data[\"api_version\"], validate=False):\n\t\t\tremote_data = Event.stripe_class.retrieve(id=local_data[\"id\"], api_key=api_key)\n\n\t\treturn local_data[\"data\"] == remote_data[\"data\"]\n\n\tdef process(self, save=True):\n\t\t# Reset traceback and exception in case of reprocessing\n\t\tself.exception = \"\"\n\t\tself.traceback = \"\"\n\n\t\tself.event = Event.process(self.json_body)\n\t\tself.processed = True\n\t\tif save:\n\t\t\tself.save()\n\n\t\treturn self.event\n", "path": "djstripe/models/webhooks.py"}]}