{"id":19339409,"url":"https://github.com/appfolio/stack-webhook-jws-examples","last_synced_at":"2026-03-19T09:21:08.093Z","repository":{"id":226316511,"uuid":"767825003","full_name":"appfolio/stack-webhook-jws-examples","owner":"appfolio","description":null,"archived":false,"fork":false,"pushed_at":"2024-04-17T23:52:24.000Z","size":145,"stargazers_count":0,"open_issues_count":2,"forks_count":1,"subscribers_count":5,"default_branch":"main","last_synced_at":"2025-01-06T10:48:24.621Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/appfolio.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-03-06T00:42:57.000Z","updated_at":"2024-03-06T23:07:06.000Z","dependencies_parsed_at":"2024-04-17T20:59:26.877Z","dependency_job_id":"fe4ecdcd-3422-4a88-a3f7-9781be6b438f","html_url":"https://github.com/appfolio/stack-webhook-jws-examples","commit_stats":null,"previous_names":["appfolio/stack-webhook-jws-examples"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/appfolio%2Fstack-webhook-jws-examples","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/appfolio%2Fstack-webhook-jws-examples/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/appfolio%2Fstack-webhook-jws-examples/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/appfolio%2Fstack-webhook-jws-examples/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/appfolio","download_url":"https://codeload.github.com/appfolio/stack-webhook-jws-examples/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":240442409,"owners_count":19801886,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-10T03:22:01.153Z","updated_at":"2026-03-19T09:21:08.082Z","avatar_url":"https://github.com/appfolio.png","language":null,"funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003e # ❗️ Usage Notice: No Maintenance Intended ❗️ ![No Maintenance Intended](badge.svg)\n\u003e ```stack-webhook-jws-examples``` contains example code for AppFolio Stack Webhooks, for use by 3rd-party AppFolio Stack Partner companies. We are deprecating this repo, but the example code is still available to draw from.\n\n\u003e ## Can I still use this code?\n\u003e **Security vulnerabilities may exist in the project, or its dependencies.** If you plan to reuse or run any code from this repo, be sure to perform appropriate security checks on the code or dependencies first. If you find these samples, patterns, and convenience functions useful, we encourage you to take the code under your wing, improve them, and use it on your own. Even though AppFolio will no longer be supporting this content going forward, you may use the code at your convenience, external to the package.\n\n\u003e ## What will happen next?\n\u003e \n\u003e AppFolio will archive the `stack-webhook-jws-examples` repo and make no further edits / modifications / examples / improvements to it.\n\n---\n\n# AppFolio Webhooks\nAppFolio provides support for webhooks through the API. Webhooks are HTTP-based callback functions that allow your app to receive real-time updates about events that occur in the AppFolio system. This repository contains example code for various languages demonstrating how to verify the signature of a webhook event notification sent by AppFolio.\n\nFor documentation, or a guide on how to use webhooks, please refer to your internal AppFolio documentation.\n\n## Examples\n- [Node.js](./examples/nodejs/index.js)\n- [Go](./examples/go/main.go)\n- [Ruby (Rails Controller)](./examples/ruby/rails_webhook_controller.rb)\n- [Python](./examples/python/server.py)\n\u003c!-- TODO: Not implemented/broken --\u003e\n\u003c!-- - [Java](./examples/java/main.java) --\u003e\n\n## Verify the Webhook Message Signature\nAppFolio signs all outgoing webhooks to enable recipients to verify the authenticity and integrity of the received notifications. You must verify the webhook message signature in the event notification payload sent by AppFolio by executing the following steps. Any notifications that fail the webhook message signature verification must not be processed.\n1. **Fetch Public Keys From AppFolio**\n- Send a GET request to https://api.appfolio.com/.well-known/jwks.json. The endpoint will respond with a set of keys in JSON Web Key Set (JWKS) [format](https://datatracker.ietf.org/doc/html/rfc7517#section-5):\n \n```json\n{\n  \"keys\": [\n    {\n      \"alg\": \"PS256\",\n      \"e\": \"AQAB\",\n      \"kid\": \"...\",\n      \"kty\": \"RSA\",\n      \"n\": \"...\",\n      \"use\": \"sig\"\n    }\n  ]\n}\n```\n\n2. **Read the Payload of the Webhook Request Sent by AppFolio**\n- Take note of the value of the `X-JWS-Signature` header. The header is stored in the following format, however, the `BASE64URL(JWS Payload)` will be missing because AppFolio is using the detached payload:\n- ``BASE64URL(UTF8(JWS Protected Header)) || '.' || BASE64URL(JWS Payload) || '.' || BASE64URL(JWS Signature)``\n```json\nPOST / HTTP/0.0\nHost: example.com\nContent-Type: application/json\nX-JWS-Signature: xxxxxx..xxxxxx\n{\"client_id\":\"example_id\",\"id\":\"example-event-uuid-value\",\"topic\":\"work_order_updates\",\"entity_id\":\"example-entity-uuid-value\",\"update_timestamp\":\"2023-03-27T16:55:12Z\",\"message_sent_at\":\"2023-08-28T23:18:27Z\"}\n```\n3. **Extract the `BASE64URL(UTF8(JWS Protected Header))` from the signature**. This is the part before the `..`  in the `X-JWS-Signature` header value. \n4. **Extract the `BASE64URL(JWS Signature)` from the signature**. This is the part after the `..`  in the `X-JWS-Signature header` header value.\n5. **Compute the `BASE64URL(JWS Payload)`**. Encode the body of the main request (the payload) using the unpadded alternate base64 encoding defined in [RFC 4648](https://datatracker.ietf.org/doc/html/rfc4648). \n6. **Verify the Webhook Message Signature**\n- Concatenate the encoded values from steps **3**, **4**, and **5** with `.` as a separator. For example: \n`BASE64URL(UTF8(JWS Protected Header)) || '.' || BASE64URL(JWS Payload) || '.' || BASE64URL(JWS Signature)`\nor:\n`encoded-jose-header + \".\" + encoded-message-payload + \".\" + jws-signature`\n- Verify the JWS signature of the computed message using the public keys fetched from **Step 1** of this section. We recommend utilizing a library that supports this verification in the coding language used for your application. Our sample code below gives examples of verifying the JWS signature of the computed message in several popular coding languages.  \n- **Note**: AppFolio uses `RSASSA_PSS_SHA_256` as the signature algorithm. For additional information pertaining to this algorithm, click [here](https://datatracker.ietf.org/doc/html/rfc3447#section-8.1).\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fappfolio%2Fstack-webhook-jws-examples","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fappfolio%2Fstack-webhook-jws-examples","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fappfolio%2Fstack-webhook-jws-examples/lists"}