Skip to main content

pharos_runtime_database/
webhook.rs

1//! Durable webhook receipt writes through the configured database adapter.
2
3use std::collections::BTreeMap;
4
5use serde_json::Value;
6
7use crate::DbBackendConfig;
8use crate::operations::{
9    AppSchema, DatabaseError, DatabaseOperations, Mutation, Transaction, WriteValue,
10};
11
12/// Records a bounded webhook payload after the caller has checked its signature.
13/// Rejected signatures are recorded as rejected receipts without trusting their payload.
14pub fn append_webhook_receipt(
15    adapter: &dyn DatabaseOperations,
16    config: &DbBackendConfig,
17    schema: &AppSchema,
18    endpoint_id: &str,
19    verified: bool,
20    payload: &str,
21) -> Result<(), DatabaseError> {
22    if endpoint_id.is_empty()
23        || endpoint_id.len() > 128
24        || !endpoint_id
25            .bytes()
26            .all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'-' | b'_'))
27        || payload.len() > 1_048_576
28    {
29        return Err(DatabaseError::InvalidInput);
30    }
31    let document: Value = serde_json::from_str(payload).map_err(|_| DatabaseError::InvalidInput)?;
32    adapter.transact(
33        config,
34        schema,
35        &Transaction {
36            steps: vec![Mutation::Insert {
37                entity: "webhook_receipt".to_owned(),
38                values: BTreeMap::from([
39                    (
40                        "endpoint_id".to_owned(),
41                        WriteValue::Literal(Value::String(endpoint_id.to_owned())),
42                    ),
43                    (
44                        "verification_status".to_owned(),
45                        WriteValue::Literal(Value::String(
46                            if verified { "verified" } else { "rejected" }.to_owned(),
47                        )),
48                    ),
49                    ("payload_json".to_owned(), WriteValue::Literal(document)),
50                ]),
51            }],
52            idempotency_key: None,
53        },
54    )?;
55    Ok(())
56}