Repository: dj-stripe/dj-stripe

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" 


Discussion:
Did you get any further with this?
Does it occur on master?

I haven't looked into where this is failing, but webhook validation in the 1.2 package is done by re-fetching the data from the Stripe API and comparing vs the webhook.

In the next major release (current master) webhook validation is done by signature verification by default, which resolves various issues with webhook validation.
@therefromhere - What are the blockers to releasing v2.0? I need many of these features. Maybe it would make sense to tackle those blocker items and get this out there, if there aren't too many.
@williamflynt I think it's just waiting on #788 to be reviewed and merged.

If you could try my #788 branch and see if you hit any issues that would help - hopefully there won't be any more migration changes beyond that before a 2.0 release, but I can't guarantee it.
Okay. I'll pull master into a project I'm trying to release and see how it goes.
For the record, I meant **I** would tackle those blocker items -- not that I thought you should. 😄 
I've been on integration testing with production systems for about a week now as well, #788, once @therefromhere rebased by merging in #784, has been working rather well, and I'll be able to volley up a PR with updated documentation, examples, a bit of clean up, as I've been writing it, as it's cropped up, along the way. 

Neither here nor there, just chiming in to say you've still got some back up on this front. Once the PR has been merged in for a beta or RC, I'll follow up on my end accordingly. If and when I hit a snag, I patch it up - being able to prove it out and show examples with the complement/supplement to the documentation will go a long way for a lot of folks waiting for this drop. 
@williamflynt since you asked there's a couple of items remaining on 2.0.0 milestone you could take a look at https://github.com/dj-stripe/dj-stripe/milestone/5
Will do.
FYI: 2.0.0 is already out :tada: 
I can partly reproduce this bug in v2.0.0 and API 2019-02-19

In my case dj-stripe does not return error 500 but the test event is not properly handled.

```
125  		@property
126  		def is_test_event(self):
127  ->			return self.json_body.get("id") == webhooks.TEST_EVENT_ID

(Pdb++) p webhooks.TEST_EVENT_ID
'evt_00000000000000'
(Pdb++) self.json_body.get("id")
'account.external_00000000000000'
```

So when Stripe sends an `account.external_account.created` test event, the id is `account.external_00000000000000`. Similarly for a `charge.expired` event, event id is `charge.expired_00000000000000`.

**Note that the first event id is not `account.external_account.created_00000000000000`**

Reading https://github.com/dj-stripe/dj-stripe/issues/392, it looks like Stripe changed the event id at some point.

Resolving patch:
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 @@ def json_body(self):
 
 	@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"
 
 


Test patch:
diff --git a/tests/test_webhooks.py b/tests/test_webhooks.py
--- a/tests/test_webhooks.py
+++ b/tests/test_webhooks.py
@@ -39,9 +39,13 @@ def _send_event(self, event_data):
 		)
 
 	def test_webhook_test_event(self):
+		self.assertEqual(WebhookEventTrigger.objects.count(), 0)
 		resp = self._send_event(FAKE_EVENT_TEST_CHARGE_SUCCEEDED)
 		self.assertEqual(resp.status_code, 200)
 		self.assertFalse(Event.objects.filter(id=TEST_EVENT_ID).exists())
+		self.assertEqual(WebhookEventTrigger.objects.count(), 1)
+		event_trigger = WebhookEventTrigger.objects.first()
+		self.assertTrue(event_trigger.is_test_event)
 
 	@override_settings(DJSTRIPE_WEBHOOK_VALIDATION="retrieve_event")
 	@patch("stripe.Transfer.retrieve", return_value=deepcopy(FAKE_TRANSFER))
@@ -215,6 +219,24 @@ def test_webhook_with_transfer_event_duplicate(
 		self.assertEqual(resp.status_code, 200)
 		self.assertEqual(1, Event.objects.filter(type="transfer.created").count())
 
+	@patch("stripe.Transfer.retrieve", return_value=deepcopy(FAKE_TRANSFER))
+	@patch("stripe.Event.retrieve")
+	def test_webhook_good(
+		self, event_retrieve_mock, transfer_retrieve_mock
+	):
+		djstripe_settings.WEBHOOK_SECRET = ""
+
+		fake_event = deepcopy(FAKE_EVENT_TRANSFER_CREATED)
+		event_retrieve_mock.return_value = fake_event
+		resp = self._send_event(fake_event)
+
+		self.assertEqual(resp.status_code, 200)
+		self.assertEqual(Event.objects.count(), 1)
+		self.assertEqual(WebhookEventTrigger.objects.count(), 1)
+
+		event_trigger = WebhookEventTrigger.objects.first()
+		self.assertEqual(event_trigger.is_test_event, False)
+
 
 class TestWebhookHandlers(TestCase):
 	def setUp(self):