We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Test webhook with 1.2.3 vs 2018.11.08 returns 500 error 'No such event: account.external_00000000000000'
Hello, I am using a Stripe account in TEST mode with API version 2018.11.08 and Django Stripe version 1.2.3. When sending a test request to test the webhook, the view returns 500 instead of 200. But when triggering real events, they are correctly handled 

In django admin or in stripe logging I can see test events have event ids such as "account.external_00000000000000" or "balance.available_00000000000000" etc but the library is checking TEST_EVENT_ID = "evt_00000000000000" 

--- 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/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 
111 			# re-raise the exception so Django sees it
112 			raise e
113 		finally:
114 			obj.save()
115 
116 		return obj
117 
118 	@cached_property
119 	def json_body(self):
120 		try:
121 			return json.loads(self.body)
122 		except ValueError:
123 			return {}
124 
125 	@property
126 	def is_test_event(self):
127 		return self.json_body.get("id") == webhooks.TEST_EVENT_ID
128 
129 	def validate(self, api_key=None):
130 		"""
131 		The original contents of the Event message must be confirmed by
132 		refetching it and comparing the fetched data with the original data.
133 
134 		This function makes an API call to Stripe to redownload the Event data
135 		and returns whether or not it matches the WebhookEventTrigger data.
136 		"""
137 
138 		local_data = self.json_body
139 		if "id" not in local_data or "livemode" not in local_data:
140 			return False
141 
142 		if self.is_test_event:
143 			logger.info("Test webhook received: {}".format(local_data))
144 			return False
145 
146 		if djstripe_settings.WEBHOOK_VALIDATION is None:
147 			# validation disabled
148 			return True
149 		elif (
150 			djstripe_settings.WEBHOOK_VALIDATION == "verify_signature"
151 			and djstripe_settings.WEBHOOK_SECRET
152 		):
153 			try:
154 				stripe.WebhookSignature.verify_header(
155 					self.body,
156 					self.headers.get("stripe-signature"),
157 					djstripe_settings.WEBHOOK_SECRET,
158 					djstripe_settings.WEBHOOK_TOLERANCE,
159 				)
160 			except stripe.error.SignatureVerificationError:
161 				return False
162 			else:
163 				return True
164 
165 		livemode = local_data["livemode"]
166 		api_key = api_key or djstripe_settings.get_default_api_key(livemode)
167 
168 		# Retrieve the event using the api_version specified in itself
169 		with stripe_temporary_api_version(local_data["api_version"], validate=False):
170 			remote_data = Event.stripe_class.retrieve(id=local_data["id"], api_key=api_key)
171 
172 		return local_data["data"] == remote_data["data"]
173 
174 	def process(self, save=True):
175 		# Reset traceback and exception in case of reprocessing
176 		self.exception = ""
177 		self.traceback = ""
178 
179 		self.event = Event.process(self.json_body)
180 		self.processed = True
181 		if save:
182 			self.save()
183 
184 		return self.event
185
```

Path: `djstripe/webhooks.py`
Content:
```
1 """
2 Utils related to processing or registering for webhooks
3 
4 A model registers itself here if it wants to be in the list of processing
5 functions for a particular webhook. Each processor will have the ability
6 to modify the event object, access event data, and do what it needs to do
7 
8 registrations are keyed by top-level event type (e.g. "invoice", "customer", etc)
9 Each registration entry is a list of processors
10 Each processor in these lists is a function to be called
11 The function signature is:
12 	<Event object>
13 
14 There is also a "global registry" which is just a list of processors (as defined above)
15 
16 NOTE: global processors are called before other processors.
17 """
18 import functools
19 import itertools
20 from collections import defaultdict
21 
22 __all__ = ["handler", "handler_all", "call_handlers"]
23 
24 
25 registrations = defaultdict(list)
26 registrations_global = list()
27 
28 TEST_EVENT_ID = "evt_00000000000000"
29 
30 
31 def handler(*event_types):
32 	"""
33 	Decorator that registers a function as a webhook handler.
34 
35 	Functions can be registered for event types (e.g. 'customer') or
36 	fully qualified event sub-types (e.g. 'customer.subscription.deleted').
37 
38 	If an event type is specified, the handler will receive callbacks for
39 	ALL webhook events of that type.  For example, if 'customer' is specified,
40 	the handler will receive events for 'customer.subscription.created',
41 	'customer.subscription.updated', etc.
42 
43 	:param event_types: The event type(s) that should be handled.
44 	:type event_types: str.
45 	"""
46 
47 	def decorator(func):
48 		for event_type in event_types:
49 			registrations[event_type].append(func)
50 		return func
51 
52 	return decorator
53 
54 
55 def handler_all(func=None):
56 	"""
57 	Decorator that registers a function as a webhook handler for ALL webhook events.
58 
59 	Handles all webhooks regardless of event type or sub-type.
60 	"""
61 	if not func:
62 		return functools.partial(handler_all)
63 
64 	registrations_global.append(func)
65 
66 	return func
67 
68 
69 def call_handlers(event):
70 	"""
71 	Invoke all handlers for the provided event type/sub-type.
72 
73 	The handlers are invoked in the following order:
74 
75 	1. Global handlers
76 	2. Event type handlers
77 	3. Event sub-type handlers
78 
79 	Handlers within each group are invoked in order of registration.
80 
81 	:param event: The event model object.
82 	:type event: ``djstripe.models.Event``
83 	"""
84 	chain = [registrations_global]
85 
86 	# Build up a list of handlers with each qualified part of the event
87 	# category and verb.  For example, "customer.subscription.created" creates:
88 	#   1. "customer"
89 	#   2. "customer.subscription"
90 	#   3. "customer.subscription.created"
91 	for index, _ in enumerate(event.parts):
92 		qualified_event_type = ".".join(event.parts[: (index + 1)])
93 		chain.append(registrations[qualified_event_type])
94 
95 	for handler_func in itertools.chain(*chain):
96 		handler_func(event=event)
97
```
--- 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
@@ -7,7 +7,6 @@
 from django.utils.functional import cached_property
 
 from .. import settings as djstripe_settings
-from .. import webhooks
 from ..context_managers import stripe_temporary_api_version
 from ..fields import JSONField
 from ..signals import webhook_processing_error
@@ -124,7 +123,8 @@
 
 	@property
 	def is_test_event(self):
-		return self.json_body.get("id") == webhooks.TEST_EVENT_ID
+		event_id = self.json_body.get("id")
+		return event_id and event_id.endswith("_00000000000000")
 
 	def validate(self, api_key=None):
 		"""
diff --git a/djstripe/webhooks.py b/djstripe/webhooks.py
--- a/djstripe/webhooks.py
+++ b/djstripe/webhooks.py
@@ -25,6 +25,8 @@
 registrations = defaultdict(list)
 registrations_global = list()
 
+# Legacy. In previous versions of Stripe API, all test events used this ID.
+# Check out issue #779 for more information.
 TEST_EVENT_ID = "evt_00000000000000"

{"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@@ -7,7 +7,6 @@\n from django.utils.functional import cached_property\n \n from .. import settings as djstripe_settings\n-from .. import webhooks\n from ..context_managers import stripe_temporary_api_version\n from ..fields import JSONField\n from ..signals import webhook_processing_error\n@@ -124,7 +123,8 @@\n \n \t@property\n \tdef is_test_event(self):\n-\t\treturn self.json_body.get(\"id\") == webhooks.TEST_EVENT_ID\n+\t\tevent_id = self.json_body.get(\"id\")\n+\t\treturn event_id and event_id.endswith(\"_00000000000000\")\n \n \tdef validate(self, api_key=None):\n \t\t\"\"\"\ndiff --git a/djstripe/webhooks.py b/djstripe/webhooks.py\n--- a/djstripe/webhooks.py\n+++ b/djstripe/webhooks.py\n@@ -25,6 +25,8 @@\n registrations = defaultdict(list)\n registrations_global = list()\n \n+# Legacy. In previous versions of Stripe API, all test events used this ID.\n+# Check out issue #779 for more information.\n TEST_EVENT_ID = \"evt_00000000000000\"\n", "issue": "Test webhook with 1.2.3 vs 2018.11.08 returns 500 error 'No such event: account.external_00000000000000'\nHello, I am using a Stripe account in TEST mode with API version 2018.11.08 and Django Stripe version 1.2.3. When sending a test request to test the webhook, the view returns 500 instead of 200. But when triggering real events, they are correctly handled \r\n\r\nIn django admin or in stripe logging I can see test events have event ids such as \"account.external_00000000000000\" or \"balance.available_00000000000000\" etc but the library is checking TEST_EVENT_ID = \"evt_00000000000000\" \n", "before_files": [{"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"}, {"content": "\"\"\"\nUtils related to processing or registering for webhooks\n\nA model registers itself here if it wants to be in the list of processing\nfunctions for a particular webhook. Each processor will have the ability\nto modify the event object, access event data, and do what it needs to do\n\nregistrations are keyed by top-level event type (e.g. \"invoice\", \"customer\", etc)\nEach registration entry is a list of processors\nEach processor in these lists is a function to be called\nThe function signature is:\n\t<Event object>\n\nThere is also a \"global registry\" which is just a list of processors (as defined above)\n\nNOTE: global processors are called before other processors.\n\"\"\"\nimport functools\nimport itertools\nfrom collections import defaultdict\n\n__all__ = [\"handler\", \"handler_all\", \"call_handlers\"]\n\n\nregistrations = defaultdict(list)\nregistrations_global = list()\n\nTEST_EVENT_ID = \"evt_00000000000000\"\n\n\ndef handler(*event_types):\n\t\"\"\"\n\tDecorator that registers a function as a webhook handler.\n\n\tFunctions can be registered for event types (e.g. 'customer') or\n\tfully qualified event sub-types (e.g. 'customer.subscription.deleted').\n\n\tIf an event type is specified, the handler will receive callbacks for\n\tALL webhook events of that type.  For example, if 'customer' is specified,\n\tthe handler will receive events for 'customer.subscription.created',\n\t'customer.subscription.updated', etc.\n\n\t:param event_types: The event type(s) that should be handled.\n\t:type event_types: str.\n\t\"\"\"\n\n\tdef decorator(func):\n\t\tfor event_type in event_types:\n\t\t\tregistrations[event_type].append(func)\n\t\treturn func\n\n\treturn decorator\n\n\ndef handler_all(func=None):\n\t\"\"\"\n\tDecorator that registers a function as a webhook handler for ALL webhook events.\n\n\tHandles all webhooks regardless of event type or sub-type.\n\t\"\"\"\n\tif not func:\n\t\treturn functools.partial(handler_all)\n\n\tregistrations_global.append(func)\n\n\treturn func\n\n\ndef call_handlers(event):\n\t\"\"\"\n\tInvoke all handlers for the provided event type/sub-type.\n\n\tThe handlers are invoked in the following order:\n\n\t1. Global handlers\n\t2. Event type handlers\n\t3. Event sub-type handlers\n\n\tHandlers within each group are invoked in order of registration.\n\n\t:param event: The event model object.\n\t:type event: ``djstripe.models.Event``\n\t\"\"\"\n\tchain = [registrations_global]\n\n\t# Build up a list of handlers with each qualified part of the event\n\t# category and verb.  For example, \"customer.subscription.created\" creates:\n\t#   1. \"customer\"\n\t#   2. \"customer.subscription\"\n\t#   3. \"customer.subscription.created\"\n\tfor index, _ in enumerate(event.parts):\n\t\tqualified_event_type = \".\".join(event.parts[: (index + 1)])\n\t\tchain.append(registrations[qualified_event_type])\n\n\tfor handler_func in itertools.chain(*chain):\n\t\thandler_func(event=event)\n", "path": "djstripe/webhooks.py"}], "after_files": [{"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 ..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\tevent_id = self.json_body.get(\"id\")\n\t\treturn event_id and event_id.endswith(\"_00000000000000\")\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"}, {"content": "\"\"\"\nUtils related to processing or registering for webhooks\n\nA model registers itself here if it wants to be in the list of processing\nfunctions for a particular webhook. Each processor will have the ability\nto modify the event object, access event data, and do what it needs to do\n\nregistrations are keyed by top-level event type (e.g. \"invoice\", \"customer\", etc)\nEach registration entry is a list of processors\nEach processor in these lists is a function to be called\nThe function signature is:\n\t<Event object>\n\nThere is also a \"global registry\" which is just a list of processors (as defined above)\n\nNOTE: global processors are called before other processors.\n\"\"\"\nimport functools\nimport itertools\nfrom collections import defaultdict\n\n__all__ = [\"handler\", \"handler_all\", \"call_handlers\"]\n\n\nregistrations = defaultdict(list)\nregistrations_global = list()\n\n# Legacy. In previous versions of Stripe API, all test events used this ID.\n# Check out issue #779 for more information.\nTEST_EVENT_ID = \"evt_00000000000000\"\n\n\ndef handler(*event_types):\n\t\"\"\"\n\tDecorator that registers a function as a webhook handler.\n\n\tFunctions can be registered for event types (e.g. 'customer') or\n\tfully qualified event sub-types (e.g. 'customer.subscription.deleted').\n\n\tIf an event type is specified, the handler will receive callbacks for\n\tALL webhook events of that type.  For example, if 'customer' is specified,\n\tthe handler will receive events for 'customer.subscription.created',\n\t'customer.subscription.updated', etc.\n\n\t:param event_types: The event type(s) that should be handled.\n\t:type event_types: str.\n\t\"\"\"\n\n\tdef decorator(func):\n\t\tfor event_type in event_types:\n\t\t\tregistrations[event_type].append(func)\n\t\treturn func\n\n\treturn decorator\n\n\ndef handler_all(func=None):\n\t\"\"\"\n\tDecorator that registers a function as a webhook handler for ALL webhook events.\n\n\tHandles all webhooks regardless of event type or sub-type.\n\t\"\"\"\n\tif not func:\n\t\treturn functools.partial(handler_all)\n\n\tregistrations_global.append(func)\n\n\treturn func\n\n\ndef call_handlers(event):\n\t\"\"\"\n\tInvoke all handlers for the provided event type/sub-type.\n\n\tThe handlers are invoked in the following order:\n\n\t1. Global handlers\n\t2. Event type handlers\n\t3. Event sub-type handlers\n\n\tHandlers within each group are invoked in order of registration.\n\n\t:param event: The event model object.\n\t:type event: ``djstripe.models.Event``\n\t\"\"\"\n\tchain = [registrations_global]\n\n\t# Build up a list of handlers with each qualified part of the event\n\t# category and verb.  For example, \"customer.subscription.created\" creates:\n\t#   1. \"customer\"\n\t#   2. \"customer.subscription\"\n\t#   3. \"customer.subscription.created\"\n\tfor index, _ in enumerate(event.parts):\n\t\tqualified_event_type = \".\".join(event.parts[: (index + 1)])\n\t\tchain.append(registrations[qualified_event_type])\n\n\tfor handler_func in itertools.chain(*chain):\n\t\thandler_func(event=event)\n", "path": "djstripe/webhooks.py"}]}