title: Discord webhook rate limits
body: <p>I'm working on a private browser extension which extracts some information from a web page and posts it into a Discord channel via a webhook.</p>
  
  <p>The browser extension does evaluate the <code>x-ratelimit-...</code> response headers to observe the rate limit restrictions.</p>
  
  <p>While doing a "spam test" it seems that the rate limit restrictions are respected correctly and everything is working so far. However, every now and then I'm still getting rate limited after sending a stack of messages (15+) even though <code>ratelimit-remaining</code> is > <code>0</code>.</p>
  
  <p>To counter this I already stop when <code>ratelimit-remaining</code> is <code>1</code> and also add an additional second to the <code>ratelimit-reset</code> timestamp. But this doesn't seem to help.</p>
  
  <pre><code>let rateLimitRemaining = 5;
  let rateLimitReset = 0;
  
  function sendContent()
  {
      if ( contentQueue.length &gt; 0 )
      {
          console.log( "Messages in content queue: " + contentQueue.length );
  
          let content = contentQueue[ 0 ];
          let dateTimestamp = getCurrentUTCTimestamp();
  
          // Don't send if remaining rate limit is &lt;= 1 and current UTC time is less than reset timestamp
          if ( rateLimitRemaining &lt;= 1 &amp;&amp; dateTimestamp &lt;= rateLimitReset )
              return;
  
          contentQueue.shift();
  
          let url = "...";
          sendMessage( content, url );
      }
  }
  
  function sendMessage( content, url )
  {
      let payload = JSON.stringify( { "content": content } );
      $.ajax(
      {
          contentType: 'application/json',
          method: "POST",
          url: url,
          data: payload,
          dataType: 'json'
      } ).done( function( response, status, jqXHR )
      {
          rateLimitRemaining = parseInt( jqXHR.getResponseHeader( 'x-ratelimit-remaining' ) );
          // Add an additional second to the reset timestamp
          rateLimitReset = parseInt( jqXHR.getResponseHeader( 'x-ratelimit-reset' ) ) + 1;
  
          let timeToResetRemaining = rateLimitReset - getCurrentUTCTimestamp();
          console.log( '[' + getCurrentDateTime() + '] Content sent to webhook. Remaining until rate limit: ' + rateLimitRemaining + ' / Reset @ ' + rateLimitReset + ' (' + getCurrentUTCTimestamp() + ') (' + timeToResetRemaining + ')' );
      } ).fail( function( jqXHR, status, error )
      {
          let response = jqXHR.responseJSON;
  
          // If we got rate limited, respect the retry_after delay
          if ( response.hasOwnProperty( 'message' ) &amp;&amp; response.message.indexOf( 'rate limited' ) !== 0 )
          {
              rateLimitRemaining = 0;
              rateLimitReset = getCurrentUTCTimestamp() + Math.ceil( response.retry_after / 1000 ) + 1;
          }
  
          console.log( '[' + getCurrentDateTime() + '] Error sending request to webhook.' );
          console.log( response );
      } );
  }
  </code></pre>
  
  <p><a href="https://i.stack.imgur.com/HOdlx.png" rel="noreferrer"><img src="https://i.stack.imgur.com/HOdlx.png" alt="Rate limited, even though remaining requests are 3"></a></p>
  
  <p>It is also strange that the same request, which triggers the rate limit, has its <code>x-ratelimit-remaining</code> response header at > 0.</p>
  
  <p><a href="https://i.stack.imgur.com/zmDzU.png" rel="noreferrer"><img src="https://i.stack.imgur.com/zmDzU.png" alt="Rate limit response header &gt; 0"></a></p>
  
  <p>What do I miss here? Where is my mistake? Do I need to take <code>x-ratelimit-bucket</code> and <code>x-ratelimit-reset-after</code> into account aswell?</p>