<?xml version="1.0" encoding="utf-8"?>
<!-- name="GENERATOR" content="github.com/mmarkdown/mmark Mmark Markdown Processor - mmark.miek.nl" -->
<rfc version="3" ipr="trust200902" docName="draft-knauer-secure-webhook-token-00" submissionType="IETF" category="info" xml:lang="en" xmlns:xi="http://www.w3.org/2001/XInclude" indexInclude="true">

<front>
<title abbrev="Secure Webhook Token (SWT)">Secure Webhook Token (SWT)</title><seriesInfo value="draft-knauer-secure-webhook-token-00" stream="IETF" status="informational" name="Internet-Draft"></seriesInfo>
<author initials="S." surname="Knauer" fullname="Stephan Knauer"><organization></organization><address><postal><street></street>
<city>Leipzig, SN</city>
<country>Germany</country>
</postal><email>mail@knauer.consulting</email>
</address></author><date year="2025" month="May" day="15"></date>
<area>Internet</area>
<workgroup>Network Working Group</workgroup>

<abstract>
<t>The Secure Webhook Token (SWT) is a specialized JSON Web Token (JWT) format designed for securely authorizing and verifying webhook requests.
It defines a set of claims to ensure that the token is explicitly tied to webhook events and that its integrity can be reliably validated by the recipient.
A key feature of SWT is the introduction of a unique claim, <tt>webhook</tt>, which encapsulates webhook-specific information, such as the event type.</t>
<t>By providing a structured and secure approach to webhook authentication, SWT aims
to be a lightweight and flexible solution while maintaining compatibility with typical JWT structures.
It is designed with the best practices in mind and may serve as a foundation for future standardization efforts.</t>
</abstract>

</front>

<middle>

<section anchor="introduction"><name>Introduction</name>
<t>The increasing use of webhooks requires a secure,
standardized approach to authorizing webhook requests and verifying the sender's authenticity.
The SWT specifies a JWT-based <xref target="RFC7519"></xref> structure, which includes unique claims tailored for webhook events.
This specification mandates that all tokens must be transmitted using either HTTP HEAD or HTTP POST requests (See <xref target="RFC9110" sectionFormat="of" section="9.3.1"></xref>),
ensuring minimal data exposure and optimal compatibility with typical HTTP implementations.</t>

<section anchor="notational-conventions"><name>Notational Conventions</name>
<t>The key words &quot;MUST&quot;, &quot;MUST NOT&quot;, &quot;REQUIRED&quot;, &quot;SHALL&quot;, &quot;SHALL NOT&quot;,
&quot;SHOULD&quot;, &quot;SHOULD NOT&quot;, &quot;RECOMMENDED&quot;, &quot;NOT RECOMMENDED&quot;, &quot;MAY&quot;, and
&quot;OPTIONAL&quot; in this document are to be interpreted as described in
&quot;Key words for use in RFCs to Indicate Requirement Levels&quot; <xref target="RFC2119"></xref>.
The interpretation should only be applied when the terms appear in
all capital letters.</t>
</section>
</section>

<section anchor="secure-webhook-token-swt-overview"><name>Secure Webhook Token (SWT) Overview</name>
<t>This specification defines required claims within the JWT <xref target="RFC7519"></xref> payload, prescribes the transport protocol as HTTP HEAD (<bcp14>RECOMMENDED</bcp14>) or HTTP POST,
and suggests a maximum payload size of 6kB for HEAD requests to remain efficient and compatible with the widely used default 8kB HTTP header size limit.
These constraints ensure interoperability across diverse systems and help prevent issues on servers with unmodified configurations.</t>
<t>To support larger payloads or non-JSON content, HTTP POST can be used as an alternative transport method, allowing the event data to be sent in the request body.</t>
</section>

<section anchor="transport-requirements"><name>Transport Requirements</name>
<t>SWTs must be transmitted via HTTP HEAD OR POST requests only.
The HEAD method <bcp14>MUST</bcp14> be used if the event data size is less than or equal to 6kB and contains valid JSON.
Otherwise, the POST method <bcp14>MUST</bcp14> be used.</t>
<t>SWTs <bcp14>MUST</bcp14> be transmitted using either HTTP HEAD or HTTP POST requests.</t>

<ul spacing="compact">
<li>The HEAD method <bcp14>SHOULD</bcp14> be used when the event data is valid JSON and the total header size is reasonably small (e.g., &lt;= 6kB).
This is based on the default 8kB header size limit enforced by many web servers.</li>
<li>The POST method <bcp14>MUST</bcp14> be used for larger payloads, non-JSON data, or when the header size exceeds what is supported by the server.</li>
</ul>
<blockquote><t>Note: There is no hard limit on the size of a HEAD request, but practical constraints exist. For example, implementations such as Go's http.Server allow configurable limits (e.g., up to 1MB headers), though such configurations should be applied cautiously due to security considerations.</t>
</blockquote></section>

<section anchor="token-size-limitation"><name>Token Size Limitation</name>
<t><strong>Recommended Maximum Token Size</strong>: While there is no strict maximum size for an SWT, a recommended
size limit of 6kB is proposed for the event data, so that the total size of the SWT is less than 8KB,
which is the default max header size of most existing HTTP servers.
This is based on the primary use case for SWTs, which is to securely authorize and trigger events rather than transmit extensive data.
The token <bcp14>SHOULD</bcp14> be kept concise, containing only essential claims to support efficient processing and minimize resource usage.</t>
<t><strong>Header Size Considerations</strong>: In practice, many web servers impose a default maximum HTTP header size of 8KB. If
servers are not configured to allow larger headers, exceeding this size <bcp14>MAY</bcp14> lead to transmission errors or dropped
requests. However, maintaining a compact token design aligns with this specification's intent to facilitate secure and lightweight webhook interactions.</t>
</section>

<section anchor="jwt-structure"><name>JWT Structure</name>
<t>An SWT comprises the following:</t>

<section anchor="header"><name>Header</name>

<ul spacing="compact">
<li><strong>alg</strong>: Specifies the signing algorithm <xref target="RFC7518"></xref>, typically <tt>&quot;HS256&quot;</tt> (HMAC SHA-256).</li>
<li><strong>typ</strong>: Specifies the token type, which <bcp14>MUST</bcp14> be <tt>&quot;JWT&quot;</tt>.</li>
</ul>
</section>

<section anchor="payload"><name>Payload</name>
<t>The payload contains the following required claims, carefully chosen to meet security and identification needs:</t>

<ul>
<li><t><strong>webhook</strong>: Custom claim specific to this specification.</t>

<ul spacing="compact">
<li><strong>event</strong>: Describes the webhook event type (e.g., <tt>&quot;payment.received&quot;</tt>).
Values should be concise to meet the size limitations.</li>
<li><strong>data</strong> (<bcp14>OPTIONAL</bcp14>): Describes the webhook data itself, which is related to the aforementioned <strong>event</strong>.</li>
</ul></li>
<li><t><strong>iss</strong> (Issuer): Identifies the token issuer (e.g., a unique name or domain representing the entity).
Short, meaningful identifiers are <bcp14>RECOMMENDED</bcp14>.</t>
</li>
<li><t><strong>sub</strong> (Subject) (<bcp14>OPTIONAL</bcp14>): While traditionally used to identify the principal subject of a JWT, the <tt>sub</tt> claim is not required in SWT.
The purpose and intent of the token are unambiguously defined by the <tt>webhook</tt> claim.
However, implementers may include <tt>sub</tt> to identify the system, service, or entity responsible for issuing the token, if such context is useful.</t>
</li>
<li><t><strong>exp</strong> (Expiration): Specifies the token's expiration time as a Unix timestamp. This prevents tokens from being valid
indefinitely.</t>
</li>
<li><t><strong>nbf</strong> (Not Before): Specifies the earliest time the token is considered valid, using a Unix timestamp.</t>
</li>
<li><t><strong>iat</strong> (Issued At): The timestamp when the token was issued, aiding in token age validation.</t>
</li>
<li><t><strong>jti</strong> (JWT ID): A unique identifier for each token to prevent replay attacks. A UUID <xref target="RFC9562"></xref> or similarly unique identifier
is <bcp14>RECOMMENDED</bcp14>.</t>
</li>
</ul>

<section anchor="example-payload-structure"><name>Example Payload Structure</name>

<section anchor="swt-payload-with-an-event-only"><name>SWT payload with an event only:</name>

<sourcecode type="json"><![CDATA[{
  "webhook": {
    "event": "ping"
  },
  "exp": 1733987961,
  "nbf": 1733987661,
  "iat": 1733987661,
  "iss": "swt.example.com",
  "jti": "2020B14D-C365-4BCF-84CD-5D423E0C6687"
}
]]></sourcecode>
</section>

<section anchor="swt-payload-with-event-and-data"><name>SWT payload with event and data:</name>

<sourcecode type="json"><![CDATA[{
  "webhook": {
    "event": "delivery.scheduled",
    "data": {
      "carrier": "UPS",
      "scheduledDeliveryDate": "2024-12-24T12:24Z",
      "recipient": {
        "name": "Lucky Kid",
        "address": {
          "locality": "Somewhere",
          "postalCode": "0815",
          "street": "Happy Place 1"
        },
        "sender": {
          "name": "Santa Claus",
          "address": {
            "locality": "North Pole",
            "postalCode": "88888",
            "street": "123 Elf Road"
          }
        }
      }
    }
  },
  "exp": 1733987961,
  "nbf": 1733987661,
  "iat": 1733987661,
  "iss": "swt.example.com",
  "jti": "2020B14D-C365-4BCF-84CD-5D423E0C6687"
}
]]></sourcecode>
</section>
</section>
</section>
</section>

<section anchor="security-considerations"><name>Security Considerations</name>

<ol>
<li><t><strong>HTTPS Transport</strong>: It is <bcp14>RECOMMENDED</bcp14> that SWT transmissions occur over HTTPS to ensure the token's
confidentiality.</t>
</li>
<li><t><strong>Replay Protection</strong>: The <tt>jti</tt> claim <bcp14>SHOULD</bcp14> be checked for uniqueness on the server side to prevent replay attacks.
Each <tt>jti</tt> value <bcp14>SHOULD</bcp14> only be accepted once.</t>
</li>
<li><t><strong>Expiration Enforcement</strong>: Systems <bcp14>MUST</bcp14> validate the <tt>exp</tt> claim to ensure that tokens cannot be used beyond their
intended validity.</t>
</li>
<li><t><strong>Respect Header Limits</strong>: Excessively large headers <bcp14>SHOULD</bcp14> be avoided. HEAD method is ideal for compact, JSON-only payloads.</t>
</li>
</ol>
</section>

<section anchor="interoperability-considerations"><name>Interoperability Considerations</name>
<t><strong>SWT's</strong> <bcp14>RECOMMENDED</bcp14> 6kB size target and support for both HEAD and POST methods ensure broad compatibility.
The structure is simple to parse and easy to integrate with existing JWT validation libraries.</t>
</section>

<section anchor="signing-and-encryption-recommendations"><name>Signing and Encryption Recommendations</name>
<t>To maintain a balance between security and usability,
SWTs are <bcp14>RECOMMENDED</bcp14> to be used as JSON Web Signatures (JWS) <xref target="RFC7515"></xref>, specifically signed JWTs.
This provides data integrity and allows the recipient to verify the token's authenticity.
The following signing algorithms are suggested for SWTs, as they offer a secure yet practical approach:</t>

<ul spacing="compact">
<li>HS256, HS384, and HS512: Symmetric algorithms using HMAC with SHA-256, SHA-384, and SHA-512, respectively. These
algorithms are considered secure and performant for most applications, especially when using shared symmetric keys
between the issuer and the receiver.</li>
</ul>
<t>While these symmetric algorithms are <bcp14>RECOMMENDED</bcp14> for ease of use and efficiency, other supported algorithms, including
asymmetric methods such as RS256 (RSA with SHA-256) or ES256 (ECDSA with SHA-256), <bcp14>MAY</bcp14> also be used if needed based on
security requirements or system constraints.</t>

<section anchor="encrypted-jwt-support-jwe"><name>Encrypted JWT Support (JWE)</name>
<t>In addition to JWS, SWT allows the use of the JSON Web Encryption (JWE) <xref target="RFC7516"></xref> standard if encrypted JWTs are
required to protect sensitive data within the token. JWE adds encryption layers to JWTs, providing confidentiality as
well as integrity protection, which may be appropriate in contexts where tokens contain sensitive information.</t>
</section>
</section>

<section anchor="iana-considerations"><name>IANA Considerations</name>
<t>This document updates the IANA &quot;JSON Web Token Claims&quot; registry
for JWT Claim Names.
Following the format in <xref target="RFC7519" sectionFormat="of" section="10.1.1"></xref>, the following
should be registered.</t>

<ul spacing="compact">
<li>Claim Name: &quot;webhook&quot;</li>
<li>Claim Description: Webhook</li>
<li>Change Controller: IESG</li>
<li>Specification Document(s): <xref target="webhook-claim-specification"></xref></li>
</ul>

<section anchor="webhook-claim-specification"><name>JWT <tt>webhook</tt> claim</name>
<t>The <tt>webhook</tt> claim is a JSON object that describes the event to be triggered and, optionally, the data related to that event.</t>
<t>It consists of the following fields:</t>

<ul spacing="compact">
<li><tt>event</tt>: A case-sensitive string describing the name of the webhook event. It should be concise and specific (e.g., <tt>&quot;order.created&quot;</tt>).</li>
<li><tt>data</tt> (<bcp14>OPTIONAL</bcp14>): Describes the event data. The handling of this field depends on the transport method used (HEAD or POST):</li>
</ul>
<blockquote><t>Although <bcp14>NOT RECOMMENDED</bcp14>, it may be necessary to send non-JSON event data via SWT.
The HTTP <em>Content-Type</em> header <bcp14>SHOULD</bcp14> be set accordingly if using the POST method.</t>
</blockquote>
<section anchor="case-1-http-head-requests"><name>Case 1: HTTP HEAD Requests</name>
<t>If the event data is valid JSON and small (&lt;= 6kB), it <bcp14>MAY</bcp14> be included inline in the <tt>data</tt> field of the JWT. In this case:</t>

<ul spacing="compact">
<li>Use of <tt>data</tt> is <bcp14>OPTIONAL</bcp14>.</li>
<li>Sending <tt>&quot;data&quot;: null</tt> is equivalent to omitting the field.</li>
<li>This is the <bcp14>RECOMMENDED</bcp14> case, as it minimizes complexity and improves performance.</li>
</ul>
</section>

<section anchor="case-2-http-post-requests"><name>Case 2: HTTP POST Requests</name>
<t>For larger payloads or non-JSON content types, the data <bcp14>MUST</bcp14> be sent in the POST request body. The <tt>data</tt> field in the JWT's <tt>webhook</tt> claim must contain a descriptor object with the following fields:</t>

<ul spacing="compact">
<li><tt>hash</tt>: The hash digest of the body payload (base64- or hex-encoded).</li>
<li><tt>size</tt>: The exact byte size of the event data to be sent in the request body.</li>
<li><tt>hashAlg</tt> (<bcp14>RECOMMENDED</bcp14>): The algorithm used to compute the hash. If omitted, the default <bcp14>SHOULD</bcp14> be assumed to be <tt>sha3-256</tt>.</li>
</ul>

<section anchor="supported-hashalg-values"><name>Supported <tt>hashAlg</tt> values:</name>
<t>The <tt>hashAlg</tt> value <bcp14>MUST</bcp14> be one of the following:</t>

<ul spacing="compact">
<li><tt>sha256</tt></li>
<li><tt>sha384</tt></li>
<li><tt>sha512</tt></li>
<li><tt>sha3-256</tt></li>
<li><tt>sha3-384</tt></li>
<li><tt>sha3-512</tt></li>
</ul>
<t>Implementers <bcp14>MAY</bcp14> restrict the set of supported algorithms based on their security posture.</t>
<blockquote><t>If <tt>hashAlg</tt> is omitted, receivers <bcp14>SHOULD</bcp14> assume the use of <tt>sha3-256</tt> by default. Senders and receivers <bcp14>SHOULD</bcp14> agree on supported algorithms in advance.</t>
</blockquote></section>
</section>

<section anchor="example-of-jwt-webhook-claim-for-head-method"><name>Example of JWT <tt>webhook</tt> claim for HEAD Method</name>

<sourcecode type="json"><![CDATA["webhook": {
  "event": "delivery.scheduled",
  "data": {
    "carrier": "UPS",
    "scheduledDeliveryDate": "2024-12-24T12:24Z",
    "recipient": {
      "name": "Lucky Kid",
      "address": {
        "locality": "Somewhere",
        "postalCode": "0815",
        "street": "Happy Place 1"
      },
      "sender": {
        "name": "Santa Claus",
        "address": {
          "locality": "North Pole",
          "postalCode": "88888",
          "street": "123 Elf Road"
        }
      }
    }
  }
}
]]></sourcecode>
</section>

<section anchor="example-of-jwt-webhook-claim-for-post-method"><name>Example of JWT <tt>webhook</tt> claim for POST Method</name>

<sourcecode type="json"><![CDATA["webhook": {
  "event": "issue.opened",
  "data": {
    "hash": "ab6e46d9c488...",
    "hashAlg": "sha3-256",
    "size": 6123
  }
}
]]></sourcecode>
</section>
</section>
</section>

<section anchor="validation"><name>Validation</name>
<t>To validate an SWT, the receiving system <bcp14>MUST</bcp14> distinguish between the HTTP method used (HEAD or POST):</t>

<ol spacing="compact">
<li>HTTP HEAD method. Sending an SWT via HTTP HEAD method, the received SWT is valid only if the JWT itself is valid.</li>
<li>HTTP POST method. Sending an SWT via HTTP POST method, the received SWT <bcp14>MUST</bcp14> be a valid JWT and contain the
<tt>webhook</tt> claim with the hash and size values of event data to be sent in the body. If either the size or the hash of the
payload is different from the ones specified within the SWT <tt>webhook</tt> claim then it <bcp14>MUST</bcp14> be rejected.</li>
</ol>
<t>If validation fails, the server <bcp14>SHOULD</bcp14> respond with a 400 Bad Request or 403 Forbidden, depending on the context.</t>

<section anchor="examples"><name>Examples:</name>

<section anchor="case-1-data-size-is-smaller-6kb-and-is-sent-via-http-head-request-directly-with-additional-data"><name>Case 1: Data size is smaller &lt;= 6kB and is sent via HTTP-HEAD request directly with additional data</name>
<t><strong>Webhook claim:</strong></t>

<sourcecode type="json"><![CDATA[ {
  "webhook": {
    "event": "delivery.scheduled",
    "data": {
      "carrier": "UPS",
      "scheduledDeliveryDate": "2024-12-24T12:24Z",
      "recipient": {
        "name": "Lucky Kid",
        "address": {
          "locality": "Somewhere",
          "postalCode": "0815",
          "street": "Happy Place 1"
        },
        "sender": {
          "name": "Santa Claus",
          "address": {
            "locality": "North Pole",
            "postalCode": "88888",
            "street": "123 Elf Road"
          }
        }
      }
    }
  }
} 
]]></sourcecode>
<t><strong>HEAD request:</strong></t>

<sourcecode type="txt"><![CDATA[HEAD / HTTP/1.1
Content-Type: application/json
Authorization: Bearer eyJ0eXAiOiJKV...
Host: localhost
]]></sourcecode>
</section>

<section anchor="case-2-data-size-is-6kb-send-the-jwt-webhook-claim-must-contain-only-hash-and-size-values-of-the-data-to-be-sent-the-actual-data-is-send-in-the-post-request-body"><name>Case 2: Data size is &gt; 6kB send the JWT <tt>webhook</tt> claim <bcp14>MUST</bcp14> contain only &quot;hash&quot; and &quot;size&quot; values of the data to be sent. The actual data is send in the POST request body.</name>
<t><strong>Webhook claim:</strong></t>

<sourcecode type="json"><![CDATA[{
  "webhook": {
    "event": "issue.opened",
    "data": {
      "hash": "ab6e46d9c488...",
      "size": 6123
    }
  }
}
]]></sourcecode>
<t><strong>POST request:</strong></t>

<sourcecode type="txt"><![CDATA[POST / HTTP/1.1
Content-Length: 6123
Content-Type: application/json
Authorization: Bearer eyJ0eXAiOiJK...
Host: localhost

{
   "action": "opened",
   "issue": {
     "url": "https://api.github.com/repos/octocat/...",
     "number": 1347,
     ...
   },
   "repository" : {
     "id": 1296269,
     "full_name": "octocat/Hello-World",
     "owner": {
       "login": "octocat",
       "id": 1,
       ...
     },
     ...
   },
   "sender": {
     "login": "octocat",
     "id": 1,
     ...
   }
 }
]]></sourcecode>
</section>
</section>
</section>

<section anchor="conclusion"><name>Conclusion</name>
<t>The Secure Webhook Token (SWT) format provides a secure,
interoperable, and efficient solution for webhook authentication.
Its focus on signed payloads, minimal overhead,
and clear transport guidance makes it suitable for modern web service ecosystems.</t>
</section>

</middle>

<back>
<references><name>References</name>
<references><name>Normative References</name>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.2119.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.7515.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.7516.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.7518.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.7519.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.9110.xml"/>
</references>
<references><name>Informative References</name>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.9562.xml"/>
</references>
</references>

</back>

</rfc>
