Sign Up

Sign Up to our social questions and Answers Engine to ask questions, answer people’s questions, and connect with other people.

Have an account? Sign In

Have an account? Sign In Now

Sign In

Login to our social questions & Answers Engine to ask questions answer people’s questions & connect with other people.

Sign Up Here

Forgot Password?

Don't have account, Sign Up Here

Forgot Password

Lost your password? Please enter your email address. You will receive a link and will create a new password via email.

Have an account? Sign In Now

Sorry, you do not have permission to ask a question, You must login to ask a question.

Forgot Password?

Need An Account, Sign Up Here

Please type your username.

Please type your E-Mail.

Please choose an appropriate title for the post.

Please choose the appropriate section so your post can be easily searched.

Please choose suitable Keywords Ex: post, video.

Browse

Need An Account, Sign Up Here

Please briefly explain why you feel this question should be reported.

Please briefly explain why you feel this answer should be reported.

Please briefly explain why you feel this user should be reported.

Sign InSign Up

Querify Question Shop: Explore Expert Solutions and Unique Q&A Merchandise

Querify Question Shop: Explore Expert Solutions and Unique Q&A Merchandise Logo Querify Question Shop: Explore Expert Solutions and Unique Q&A Merchandise Logo

Querify Question Shop: Explore Expert Solutions and Unique Q&A Merchandise Navigation

  • Home
  • About Us
  • Contact Us
Search
Ask A Question

Mobile menu

Close
Ask a Question
  • Home
  • About Us
  • Contact Us
Home/ Questions/Q 5210

Querify Question Shop: Explore Expert Solutions and Unique Q&A Merchandise Latest Questions

Author
  • 61k
Author
Asked: November 27, 20242024-11-27T07:08:07+00:00 2024-11-27T07:08:07+00:00

Handling duplicate events from Stripe in your webhook endpoint

  • 61k

In my recent post, detailing how I handle order fulfillment for my Stripe integration, I missed an important part of reacting to Stripe webhooks. The documentation explains that your endpoint may be called multiple times for a single event, but I don’t handle that in my code.

It also points out the ordering of events is not guaranteed, but that doesn’t matter in my case since I’m only handling one event type.

The issue with duplicate events

Every time I receive an event, my original implementation would push a new message into a queue and the order fulfillment would continue. If I receive a duplicate CheckoutSessionCompleted event, it isn’t a terrible problem; a customer might receive multiple emails from me with their photo. Each one would be a slightly different link though and, in addition to looking sloppy, it could cause them to worry they were charged multiple times. In many scenarios, this could be a larger problem; shipping two physical items, or signing them up for multiple digital purchases.

For all of those reasons, I’ve updated my code. Now, when I receive an event, I check a CosmosDB container to see if I’ve handled this event ID before.

(from Functions.cs)

OrderData data = await CreateOrderData();  bool exists =  await data.checkForExisting("checkoutComplete",  order.SessionID);  if (!exists) {  await queueClient.SendMessageAsync(message); } else {  log.LogInformation(  $"Duplicate Event: {order.EventID}"); }  await data.insertLogItem("checkoutComplete",  order.SessionID, order.EventID, exists);  
Enter fullscreen mode Exit fullscreen mode

If so, I skip pushing a message into the incoming order queue and return a 200 OK result. If I haven’t seen this event ID, I go ahead and push the message in and then log (insertLogItem) that I processed this event. I log when I see a duplicate as well, just in case I’m interested in the future.

This required adding a data storage class to my project, where I encapsulated all the initialization of Cosmos DB and handling both the check and insert steps.

(from OrderData.cs)

public async Task insertLogItem(string functionName,  string sessionID, string eventID, bool duplicate) {  LogItem item = new LogItem()  {  function = functionName,  checkoutSessionID = sessionID,  eventID = eventID,  duplicate = duplicate  };   await this.c_functionLog.CreateItemAsync<LogItem>(  item, new PartitionKey(functionName)); }   public async Task<Boolean> checkForExisting(  string functionName,  string checkoutSessionID) {  string query = "SELECT c.id " +  "FROM c WHERE c.checkoutSessionID =" +  "@checkoutSessionID AND " +  "c.function=@functionName";   QueryDefinition q = new QueryDefinition(query)  .WithParameter("@checkoutSessionID", checkoutSessionID)  .WithParameter("@functionName", functionName);   using (FeedIterator<LogItem> feedIterator  = this.c_functionLog.GetItemQueryIterator<LogItem>(q))  {  if (feedIterator.HasMoreResults)  {  FeedResponse<LogItem> response  = await feedIterator.ReadNextAsync();   if (response != null && response.Count > 0)  {  return true;  }  }  }  return false; }  
Enter fullscreen mode Exit fullscreen mode

This is not a perfect solution.

My Azure Function could be running on multiple threads (and/or servers), handling multiple requests at once, and therefore potentially posting duplicate messages.

I could avoid this by using a semaphore/locking mechanism, so that checking for an existing log entry, pushing the incoming order message, and adding a new log entry all happened as an isolated transaction. Doing this would reduce my Function’s ability to handle a high load of requests though, and while I don’t expect that to matter in my specific case, it seems like a bad pattern that someone may copy.

Instead, I’m going to go with a ‘belt and suspenders’ model (multiple preventative methods to avoid an issue), by adding another similar check to the second and third functions.

I could also force these functions to process messages one at a time through some configuration options. This would reduce their scalability, as discussed for the first function, but that is less of an issue for these stages in the order fulfillment.

Having written the check and insert as discrete functions makes it easy to repeat the pattern in each of the two other methods.

OrderData data = await CreateOrderData();  bool exists = await data.checkForExisting(  "processOrder", order.SessionID);  if (exists) {  log.LogInformation(  $"Duplicate Event: {order.SessionID}");  return; }  //do all the work  await data.insertLogItem(  "processOrder", order.SessionID,  order.EventID, exists);  
Enter fullscreen mode Exit fullscreen mode

Note that I had planned this in my head from the start of making these changes, which was why I used the function name as the partition key, and then the appropriate unique identifier as the id.

After adding these checks to the other functions, I will remove them from the initial HTTP handler. My goal with that webhook endpoint is to return as fast as possible, these data calls are quick but they still add time. Adding multiple entries to the incoming Order queue doesn’t have any negative impact, as long as the processOrder function knows to skip duplicates.

Wrapping up

These modifications have the positive side effect of giving me a nice, easy to read, log file of all the function executions.

Three CosmosDB records showing all three functions running once each

As you build out your own webhook handlers, refer back to the Stripe webhook documentation for notes on this and other implementation details to be aware of.

azurecodingstripewebdev
  • 0 0 Answers
  • 0 Views
  • 0 Followers
  • 0
Share
  • Facebook
  • Report

Leave an answer
Cancel reply

You must login to add an answer.

Forgot Password?

Need An Account, Sign Up Here

Sidebar

Ask A Question

Stats

  • Questions 4k
  • Answers 0
  • Best Answers 0
  • Users 1k
  • Popular
  • Answers
  • Author

    How to ensure that all the routes on my Symfony ...

    • 0 Answers
  • Author

    Insights into Forms in Flask

    • 0 Answers
  • Author

    Kick Start Your Next Project With Holo Theme

    • 0 Answers

Top Members

Samantha Carter

Samantha Carter

  • 0 Questions
  • 20 Points
Begginer
Ella Lewis

Ella Lewis

  • 0 Questions
  • 20 Points
Begginer
Isaac Anderson

Isaac Anderson

  • 0 Questions
  • 20 Points
Begginer

Explore

  • Home
  • Add group
  • Groups page
  • Communities
  • Questions
    • New Questions
    • Trending Questions
    • Must read Questions
    • Hot Questions
  • Polls
  • Tags
  • Badges
  • Users
  • Help

Footer

Querify Question Shop: Explore Expert Solutions and Unique Q&A Merchandise

Querify Question Shop: Explore, ask, and connect. Join our vibrant Q&A community today!

About Us

  • About Us
  • Contact Us
  • All Users

Legal Stuff

  • Terms of Use
  • Privacy Policy
  • Cookie Policy

Help

  • Knowledge Base
  • Support

Follow

© 2022 Querify Question. All Rights Reserved

Insert/edit link

Enter the destination URL

Or link to existing content

    No search term specified. Showing recent items. Search or use up and down arrow keys to select an item.