Skip to main content

Module webhook

Module webhook 

Source
Expand description

Webhooks: calls from payment gateways and other services, received safely. For each call Renox:

  1. checks it came from the provider (Webhook::verify, usually a signature over the raw body), answering 401 otherwise;
  2. stores it in webhook_calls, once per provider event id, so the provider’s retries of the same event are answered 200 and not processed twice;
  3. answers 200 at once and runs Webhook::handle in a queue worker, retrying on errors; webhook:failed lists what failed and webhook:retry <id> runs a call again.
use renox::webhook;

struct Xendit;

impl Webhook for Xendit {
    const PROVIDER: &'static str = "xendit";

    fn verify(req: &WebhookRequest, state: &AppState) -> Result {
        let token = webhook::secret(state, "XENDIT_CALLBACK_TOKEN")?;
        webhook::ensure(req.header("x-callback-token").is_some_and(|t| webhook::same(t, &token)))
    }

    fn event_id(req: &WebhookRequest) -> Result<String> {
        let invoice: Invoice = req.json()?;
        Ok(format!("{}:{}", invoice.id, invoice.status))
    }

    async fn handle(call: WebhookCall, ctx: JobContext) -> Result {
        let invoice: Invoice = call.json()?;
        mark_paid(&ctx.state.db, &invoice.external_id).await
    }
}

// Module::routes:   Routes::new().webhook::<Xendit>("/webhooks/xendit")
// Module::register: app.webhook::<Xendit>();

Webhook routes skip CSRF (callers have no session) and keep working in maintenance mode (calls are stored and processed as usual).

Structs§

WebhookCall
A stored call, as handle gets it.
WebhookRequest
The incoming call: headers and the raw body, exactly as signed.

Enums§

WebhookStatus
Where a stored webhook call is, in webhook_calls.status.

Traits§

Webhook
Tells Renox how to receive one provider’s webhooks.

Functions§

ensure
Ok if valid, otherwise the error verify returns for a forged call.
hmac_sha256_hex
Lowercase hex HMAC-SHA256 of data with key.
hmac_sha512_hex
Lowercase hex HMAC-SHA512 of data with key.
retry
Runs a stored call again (e.g. after fixing a bug); false if there’s no such call.
same
Compares two strings in time independent of where they differ.
secret
A secret from .env (or Config::vars), e.g. secret(state, "MIDTRANS_SERVER_KEY"); missing is an error.
sha256_hex
Lowercase hex SHA-256 of data.
sha512_hex
Lowercase hex SHA-512 of data (Midtrans’ signature_key).
verify_hmac_sha256
Checks a hex HMAC-SHA256 signature of body, with or without a sha256= prefix (GitHub, Shopify-style hex, …), ignoring hex case.
verify_timestamped
Checks a Stripe-style signature header, t=<unix time>,v1=<hex>[,v1=…], where each v1 is HMAC-SHA256 of "{t}.{body}", and refuses one older or newer than tolerance (Stripe uses five minutes) so it can’t be replayed.