Tilitfy webhook changes (#6267)

* Tilitfy webhook changes

* add env

* fix
This commit is contained in:
aecsocket
2026-06-02 14:14:02 +01:00
committed by GitHub
parent cfe45b368c
commit d61397097c
5 changed files with 62 additions and 4 deletions
+1 -1
View File
@@ -64,7 +64,7 @@ The website and app `prepr` commands
Each project may have its own `CLAUDE.md` with detailed instructions:
- [`apps/labrinth/CLAUDE.md`](apps/labrinth/CLAUDE.md) — Backend API
- [`apps/labrinth/AGENTS.md`](apps/labrinth/AGENTS.md) — Backend API
- [`apps/frontend/CLAUDE.md`](apps/frontend/CLAUDE.md) - Frontend Website
## Code Guidelines
+1
View File
@@ -104,6 +104,7 @@ TREMENDOUS_PRIVATE_KEY=none
TREMENDOUS_CAMPAIGN_ID=none
TILTIFY_CLIENT_ID=
TILTIFY_CLIENT_SECRET=
TILTIFY_WEBHOOK_SIGNING_KEY=
TILTIFY_PRIDE_26_CAMPAIGN_ID=
HCAPTCHA_SECRET=none
+1
View File
@@ -125,6 +125,7 @@ TREMENDOUS_PRIVATE_KEY=none
TREMENDOUS_CAMPAIGN_ID=none
TILTIFY_CLIENT_ID=
TILTIFY_CLIENT_SECRET=
TILTIFY_WEBHOOK_SIGNING_KEY=
TILTIFY_PRIDE_26_CAMPAIGN_ID=
HCAPTCHA_SECRET=none
+1
View File
@@ -298,6 +298,7 @@ vars! {
TILTIFY_CLIENT_ID: String = "";
TILTIFY_CLIENT_SECRET: String = "";
TILTIFY_PRIDE_26_CAMPAIGN_ID: String = "";
TILTIFY_WEBHOOK_SIGNING_KEY: String = "";
// server pinging
SERVER_PING_MAX_CONCURRENT: usize = 16usize;
+58 -3
View File
@@ -1,9 +1,12 @@
use actix_web::{get, post, web};
use chrono::{DateTime, Utc};
use actix_web::{HttpRequest, get, post, web};
use base64::Engine;
use chrono::{DateTime, Duration, Utc};
use eyre::eyre;
use hmac::{Hmac, Mac};
use reqwest::Method;
use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
use sha2::Sha256;
use std::collections::HashSet;
use tracing::{info, warn};
use uuid::Uuid;
@@ -134,11 +137,17 @@ impl CampaignDonation {
#[utoipa::path]
#[post("/webhook")]
pub async fn tiltify_webhook(
req: HttpRequest,
pool: web::Data<PgPool>,
redis: web::Data<RedisPool>,
payouts_queue: web::Data<PayoutsQueue>,
web::Json(raw_payload): web::Json<serde_json::Value>,
body: String,
) -> Result<(), ApiError> {
verify_tiltify_webhook_signature(&req, &body)?;
let raw_payload = serde_json::from_str::<serde_json::Value>(&body)
.wrap_internal_err_with(|| eyre!("invalid Tiltify webhook JSON"))?;
// deserialize the JSON in the request handler, not in the params,
// since if the JSON fails to deserialize then it's *our* fault,
// not the caller's.
@@ -237,6 +246,52 @@ pub async fn tiltify_webhook(
Ok(())
}
fn verify_tiltify_webhook_signature(
req: &HttpRequest,
body: &str,
) -> Result<(), ApiError> {
let signature = req
.headers()
.get("X-Tiltify-Signature")
.and_then(|x| x.to_str().ok())
.wrap_request_err("missing Tiltify webhook signature")?;
let signature = base64::engine::general_purpose::STANDARD
.decode(signature)
.wrap_request_err("invalid Tiltify webhook signature")?;
let timestamp = req
.headers()
.get("X-Tiltify-Timestamp")
.and_then(|x| x.to_str().ok())
.wrap_request_err("missing Tiltify webhook timestamp")?;
let parsed_timestamp = DateTime::parse_from_rfc3339(timestamp)
.wrap_request_err("invalid Tiltify webhook timestamp")?;
let parsed_timestamp = parsed_timestamp.with_timezone(&Utc);
let age = Utc::now().signed_duration_since(parsed_timestamp);
if age < -Duration::minutes(1) || age > Duration::minutes(1) {
return Err(ApiError::Request(eyre!(
"expired Tiltify webhook timestamp",
)));
}
if ENV.TILTIFY_WEBHOOK_SIGNING_KEY.is_empty() {
return Err(ApiError::Internal(eyre!(
"TILTIFY_WEBHOOK_SIGNING_KEY must be set"
)));
}
let mut mac: Hmac<Sha256> =
Hmac::new_from_slice(ENV.TILTIFY_WEBHOOK_SIGNING_KEY.as_bytes())
.wrap_internal_err("initializing Tiltify webhook HMAC")?;
mac.update(timestamp.as_bytes());
mac.update(b".");
mac.update(body.as_bytes());
mac.verify_slice(&signature)
.wrap_request_err("invalid Tiltify webhook signature")?;
Ok(())
}
#[utoipa::path]
#[get("/pride-26")]
pub async fn pride_26(