{"id":20733776,"url":"https://github.com/nubank/clj-github-app","last_synced_at":"2025-12-12T01:24:25.201Z","repository":{"id":44817394,"uuid":"145592928","full_name":"nubank/clj-github-app","owner":"nubank","description":"A library to implement GitHub Apps in Clojure.","archived":false,"fork":false,"pushed_at":"2024-12-06T20:00:05.000Z","size":55,"stargazers_count":47,"open_issues_count":1,"forks_count":4,"subscribers_count":689,"default_branch":"master","last_synced_at":"2025-03-28T07:02:58.293Z","etag":null,"topics":["authentication","client","clojure","github","webhook"],"latest_commit_sha":null,"homepage":"https://developer.github.com/apps/about-apps/#about-github-apps","language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"epl-1.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/nubank.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","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":"2018-08-21T16:49:42.000Z","updated_at":"2024-12-17T13:21:25.000Z","dependencies_parsed_at":"2024-08-06T21:53:22.024Z","dependency_job_id":"003c41ce-88d3-43e8-940d-16a27f8880b7","html_url":"https://github.com/nubank/clj-github-app","commit_stats":{"total_commits":27,"total_committers":7,"mean_commits":3.857142857142857,"dds":0.7037037037037037,"last_synced_commit":"9a2e3a436c47a5c30bc1b495c1660094af15a68c"},"previous_names":["dryewo/clj-github-app"],"tags_count":7,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nubank%2Fclj-github-app","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nubank%2Fclj-github-app/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nubank%2Fclj-github-app/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nubank%2Fclj-github-app/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/nubank","download_url":"https://codeload.github.com/nubank/clj-github-app/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247137800,"owners_count":20889927,"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":["authentication","client","clojure","github","webhook"],"created_at":"2024-11-17T05:27:03.824Z","updated_at":"2025-12-12T01:24:25.166Z","avatar_url":"https://github.com/nubank.png","language":"Clojure","readme":"# clj-github-app\n[![Build Status](https://travis-ci.org/nubank/clj-github-app.svg?branch=master)](https://travis-ci.org/nubank/clj-github-app)\n[![codecov](https://codecov.io/gh/nubank/clj-github-app/branch/master/graph/badge.svg)](https://codecov.io/gh/nubank/clj-github-app)\n[![Clojars Project](https://img.shields.io/clojars/v/nubank/clj-github-app.svg)](https://clojars.org/nubank/clj-github-app)\n\nA library to implement [GitHub Apps] in Clojure.\n\n```clj\n[nubank/clj-github-app \"0.3.0\"]\n```\n\nIncludes:\n\n* [Webhook payload signature checker][webhook-signatures] with [secure comparison](https://github.com/weavejester/crypto-equality).\n* API client with HTTP connection pool.\n* Access token manager with caching ([Authenticating with GitHub Apps] is tricky).\n\n## Usage\n\n### Checking webhook signatures\n\nWhen implementing a webhook handler, it is recommended to check the webhook request signature before processing it.\nPlease read the [official documentation][webhook-signatures] first.\n\nImagine you have a webhook handler:\n\n```clj\n(ns your-project.webhooks\n  (:require [clj-github-app.webhook-signature :as webhook-signature]))\n\n(def GITHUB_WEBHOOK_SECRET (System/getenv \"GITHUB_WEBHOOK_SECRET\"))\n\n(defn post-github\n  \"Checks if the webhook is valid and handles it.\"\n  [request]\n  (let [{:strs [x-github-delivery x-github-event x-hub-signature-256]} (:headers request)\n        payload (slurp (:body request))]\n    (case (webhook-signature/check-payload-signature-256 GITHUB_WEBHOOK_SECRET x-hub-signature-256 payload)\n      ::webhook-signature/missing-signature {:status 400 :body \"x-hub-signature-256 header is missing\"}\n      ::webhook-signature/wrong-signature {:status 401 :body \"x-hub-signature-256 does not match\"}\n      (let [parsed-payload  (json/parse-string payload keyword)]\n        ;; process your webhook here\n        {:status 200 :body \"This is fine.\"}))))\n```\n\nThe key part here is the call to  `check-payload-signature-256`. It takes 3 arguments:\n\n* `webhook-secret` — the exact secret string that you set when configuring webhook for your repo.  \n    If this argument is blank or nil, `check-payload-signature-256` will do nothing and return\n    `:clj-github-app.webhook-signature/not-checked`.\n* `x-hub-signature-256` — contents of \"X-Hub-Signature-256\" request header.\n* `payload` — request body as a string.\n\nPossible return values:\n\n* `:clj-github-app.webhook-signature/ok` — signature matches the payload.\n* `:clj-github-app.webhook-signature/wrong-signature` — signature does not match the payload.\n* `:clj-github-app.webhook-signature/missing-signature` — `x-hub-signature` parameter was blank or nil.\n* `:clj-github-app.webhook-signature/not-checked` — no check was done because `webhook-secret` parameter was blank or nil.\n\n\n### Authenticating as a GitHub App\n\nPlease read [Authenticating with GitHub Apps] official documentation first.\n\nExample (uses [mount-lite]):\n\n```clj\n(ns your-project.external.github\n  (:require [mount.lite :as m]\n            [clj-github-app.client :as client]))\n\n(def GITHUB_API_URL \"https://api.github.com\")\n(def GITHUB_APP_ID (System/getenv \"GITHUB_APP_ID\"))\n(def GITHUB_APP_PRIVATE_KEY_PEM (System/getenv \"GITHUB_APP_PRIVATE_KEY_PEM\"))\n\n(m/defstate client\n  :start (client/make-app-client GITHUB_API_URL GITHUB_APP_ID GITHUB_APP_PRIVATE_KEY_PEM {})\n  :stop (.close @client))\n```\n\n`clj-github-app.client/make-app-client` takes 4 parameters:\n\n* `github-api-url` — Base URL of GitHub API. Usually `https://api.github.com` or something like `https://github.example.com/api/v3` for GHE.\n* `github-app-id` — GitHub App ID as string (can be found on the app settings page).\n* `private-key-pem-str` — String contents of the private key file that you [generated when configuring the app](https://developer.github.com/apps/building-github-apps/authenticating-with-github-apps/#generating-a-private-key).\n* `connection-pool-opts` — [clj-http connection pool parameters](https://github.com/dakrone/clj-http#persistent-connections).\n    Can be set to `{}` to use all defaults.\n\nIt returns an object that implements `AutoCloseable` interface and `AppClient` protocol, which has the following functions:\n\n* `request*` — to [authenticate as an installation][as-installation].\n    Given `installation-id` and `opts`, makes an HTTP request to GitHub API, automatically retrieving an access token.  \n    Uses [clj-http], `opts` argument is given to `request` function as described [here](https://github.com/dakrone/clj-http#raw-request).\n    `opts` is supposed to include `:method` and `:url` keys.\n    This function is the main workhorse.\n* `request` — same as `request*`, but has separate arguments for method and URL.\n* `app-request*` — to [authenticate as a GitHub App][as-app].\n    This is only useful for querying app metadata.\n* `app-request` — same as `app-request*`, but has separate arguments for method and URL.\n\nYou can [authenticate as a GitHub App][as-app]:\n\n```clj\n(client/app-request* @client {:method :get :url \"/app\" :accept \"application/vnd.github.machine-man-preview+json\"})\n(client/app-request @client :get \"/app\" {:accept \"application/vnd.github.machine-man-preview+json\"})\n```\n\nYou can also [authenticate as an installation][as-installation]. For this you need Installation ID,\n(which is usually given to you in webhook payloads):\n\n```clj\n(client/request* @client 42 {:method :get :url \"/repos/myname/myrepo/issues/123/comments\")\n(client/request @client 42 :get \"/repos/myname/myrepo/issues/123/comments\" {})\n```\n\nAll these functions can accept either a full URL or just a relative path, which will be automatically appended to the base\nGitHub API URL, given earlier to `make-app-client`.  \nThe \"path only\" mode is useful when you are constructing the URL yourself and don't want to repeat the base API URL there.\nThe path can start with a `/` or not, which makes no difference, both cases are handled the same way.\nThe \"full URL\" mode is useful when you use a URL extracted from a webhook payload\nand don't want to strip the base URL part from there.\n\n```clj\n;; Use github-api-url (provided earlier to make-app-client) as base API URL\n(client/app-request @client :get \"foo\" {})\n(client/app-request @client :get \"/foo\" {})\n;; The same call, but without relying on github-api-url\n(client/app-request @client :get \"https://api.github.com/foo\" {})\n```\n\n#### Convenience wrappers for API endpoints\n\nThis library does not provide any wrappers like\n\n```clj\n(list-issue-comments \"owner\" \"repo\" \"123\" {:since \"2018-01-01\"})\n```\n\nSuch wrappers are really easy to implement on your own:\n\n```clj\n(defn create-list-issue-comments-request [owner repo issue-number params]\n  {:method       :get\n   :url          (format \"/repos/%s/%s/issues/%s/comments\" owner repo issue-number)\n   :query-params params})\n```\n\nand then use like this:\n\n```clj\n(client/request @client 42 (create-list-issue-comments-request \"owner\" \"repo\" \"123\" {:since \"2018-01-01\"}))\n```\n\nFull GitHub API reference can be found [here](https://developer.github.com/v3/).\n\n## Development\n\nWith every commit, add important changes from it to the \"Unreleased\" section of _CHANGELOG.md_.\n\n### Release procedure\n\nRun `lein release` as described below, depending on how much changes are made since previous release.\n\nLibrary version will be updated in _project.clj_ and _README.md_ automatically after calling `lein release`.\n`## Unreleased` section int _CHANGELOG.md_ will be automatically changed into the version being released.\n\n    lein release :patch\n    # or\n    lein release :minor\n    # or\n    lein release :major\n\n## License\n\nCopyright © 2018 Dmitrii Balakhonskii\n\nDistributed under the Eclipse Public License version 1.0.\n\n\n[GitHub Apps]: https://developer.github.com/apps/about-apps/#about-github-apps\n[Authenticating with GitHub Apps]: https://developer.github.com/apps/building-github-apps/authenticating-with-github-apps/\n[webhook-signatures]: https://developer.github.com/webhooks/securing/#validating-payloads-from-github\n[as-app]: https://developer.github.com/apps/building-github-apps/authenticating-with-github-apps/#accessing-api-endpoints-as-a-github-app\n[as-installation]: https://developer.github.com/apps/building-github-apps/authenticating-with-github-apps/#accessing-api-endpoints-as-an-installation\n[clj-http]: https://github.com/dakrone/clj-http\n[mount-lite]: https://github.com/aroemers/mount-lite\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnubank%2Fclj-github-app","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnubank%2Fclj-github-app","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnubank%2Fclj-github-app/lists"}