Skip to content

Instantly share code, notes, and snippets.

@Fyko
Last active April 23, 2024 10:26
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Fyko/2c80a03b02219b082b68b337ed88503f to your computer and use it in GitHub Desktop.
Save Fyko/2c80a03b02219b082b68b337ed88503f to your computer and use it in GitHub Desktop.
a clerk webhook verifier for axum
///! An [axum](https://docs.rs/axum) extractor for [Clerk webhooks](https://clerk.com/docs/integration/webhooks).
use axum::{
body::Bytes,
extract::FromRequest,
http::{Request, StatusCode},
response::{IntoResponse, Response},
};
// TODO: replace with your config
use happycamper_util::config::CONFIG;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use svix::webhooks::Webhook;
/// A structure and axum extractor representing a Clerk webhook.
/// ```rs
/// async fn clerk_webhook(webhook: ClerkWebhook) -> Result<Response<String>> {
/// /// your business logic
/// }
#[derive(Debug, Deserialize)]
pub struct ClerkWebhook {
pub object: String,
#[serde(rename = "type")]
pub kind: String,
pub data: Value,
}
#[async_trait::async_trait]
impl<S, B> FromRequest<S, B> for ClerkWebhook
where
S: Send + Sync,
B: Send + 'static,
Bytes: FromRequest<S, B>,
{
type Rejection = Response;
async fn from_request(req: Request<B>, state: &S) -> Result<Self, Self::Rejection> {
let headers = req.headers().clone();
let payload = Bytes::from_request(req, state)
.await
.map_err(IntoResponse::into_response)?;
// TODO: replace with your config
let webhook = Webhook::new(&CONFIG.clerk_webhook_key).expect("Invalid clerk webhook key");
webhook.verify(&payload, &headers).map_err(|e| {
(StatusCode::BAD_REQUEST, format!("Failed to verify: {}", e)).into_response()
})?;
let data: ClerkWebhook = serde_json::from_slice(&payload)
.map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "Failed to parse").into_response())?;
Ok(data)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment