Skip to main content

Module webhooks

Module webhooks 

Source
Expand description

Webhooks: signatures for calls in both directions, and processing each event once.

A payment provider, a GPU service or a mail service calls the app back with a signed POST. The handler checks the signature over the raw body, then runs the effect inside once, which records the event in the webhook_events table (ocre g webhook creates it) and skips a delivery it has already processed: providers retry until they get a 2xx, so the same event often arrives twice.

use axum::{body::Bytes, extract::State, http::HeaderMap};
use ocre::{Ctx, Result, webhooks};


async fn receive(State(ctx): State<Ctx>, headers: HeaderMap, body: Bytes) -> Result<&'static str> {
    let secret = ctx.secret("PAYMENTS_WEBHOOK_SECRET").await?;
    let signature = headers.get("x-signature").and_then(|v| v.to_str().ok()).unwrap_or_default();
    webhooks::verify(secret.as_bytes(), &body, signature)?;
    let event: serde_json::Value =
        serde_json::from_slice(&body).map_err(|_| ocre::Error::bad_request("invalid JSON"))?;
    let id = event["id"].as_str().unwrap_or_default().to_owned();
    let db = ctx.db()?;
    webhooks::once(&db, "payments", &id, &body, || async {
        // ... mark the order paid: runs once per event id
        Ok(())
    })
    .await?;
    Ok("ok")
}

sign is the other direction: the app signs what it sends (a job submitted to an external service), and the service checks it the same way.

Structs§

Answer
The answer of post_signed: status and body.

Enums§

Delivery
What once did with a delivery.

Constants§

STALE_AFTER
A delivery left processing this long (seconds) is taken over by the next one: the invocation that claimed it died before finishing.
TABLE_SQL
The webhook_events table once records deliveries in, created by the migration of ocre g webhook.

Functions§

once
Runs effect for the event event_id of source unless it already ran.
post_signed
POSTs body as JSON to url, signed: the HMAC-SHA256 of the body with secret in X-Signature (lowercase hex, see sign), and Authorization: Bearer <bearer> when given (API keys of services such as RunPod). One subrequest. Any status is an answer: check status (a service’s 4xx is not an error of the call).
sign
HMAC-SHA256 of message with secret, as lowercase hex: the signature to send in a header (X-Signature) of an outgoing call.
sign_standard
The webhook-signature header value (v1,<base64>) of a Standard Webhooks delivery of body with this id and timestamp (Unix seconds): what verify_standard checks. For an app that sends Standard Webhooks, and for tests of one that receives them.
verify
Checks that signature is the HMAC-SHA256 of message with secret, in constant time. The signature may be hex (any case, optionally prefixed sha256= as GitHub sends it) or base64 (standard or URL-safe, padded or not), the common encodings of webhook providers. Sign the raw request body: parsed and re-serialized JSON may differ by a space.
verify_standard
Checks a Standard Webhooks delivery (Svix, Resend, and other providers): webhook-signature holds one or more space-separated v1,<base64> signatures of {id}.{timestamp}.{body} keyed with the base64 part of the whsec_... secret, and the webhook-timestamp must be within tolerance seconds of now (replays of an old delivery are refused). Returns the webhook-id, the event id to give once.