Search the site
BACKEND
Tell your customers' systems when something happens, with signed and retried deliveries.
Declare an event, let them subscribe, then emit it.
ON THIS PAGE
Declare, subscribe and emit
Deliver and retry
Choose the envelope and the signature
Verify a delivery on the receiving side
Publish the event catalog
Status
DV.Webhooks.declare(const DVWebhookEvent(
'order.paid',
sensitiveFields: <String>{'cardLast4'},
));
await DV.Webhooks.subscribe(
url: 'https://partner.example.com/hooks',
events: <String>{'order.paid'},
signingSecret: 'PARTNER_WEBHOOK_SECRET', // a secret's name
);
await DV.Webhooks.emit('order.paid', <String, Object?>{'orderId': 'o-42'});Copy code to clipboard
Emitting an event nobody declared throws DVWebhookUndeclaredEventException.
sensitiveFields never leave in a payload, wherever they appear.
signingSecret is the name of a secret, read through DV.Secrets.
drainAll() sends pending deliveries. Failures retry up to 5 times, 30 seconds apart by default.
A subscription is disabled after 20 failures in a row.
deliveries(id) and replay(deliveryId) let you inspect and resend.
With no dartvel.webhooks block, deliveries use Dartvel's envelope and signature. Existing subscribers see no change.
# pubspec.yaml
dartvel:
webhooks:
format: cloudevents # dartvel (default) | cloudevents
mode: structured # or binary: ce- headers, data as the body
source: https://shop.example.com
signature: standard # dartvel (default) | standardCopy code to clipboard
format: cloudevents sends each delivery as a CloudEvents 1.0.2 event. Its id is the delivery id and its type is the event name.
mode: structured sends application/cloudevents+json, so every attribute is inside the signed body. mode: binary puts the attributes in ce- headers, which the signature does not cover.
source defaults to /<your package name>.
signature: standard signs the Standard Webhooks way, with webhook-id, webhook-timestamp and webhook-signature. The subscription's secret must hold a whsec_ key.
A key or value Dartvel does not know stops dartvel build with DV-WEBHOOK-009.
By default each request carries dartvel-webhook-id, -event, -timestamp and -signature headers. The signature is an HMAC SHA-256 of the timestamp and the body.
bool isGenuine(Map<String, String> headers, String body, String secret) =>
DVWebhookSignature.verify(
secret: secret,
timestamp: headers['dartvel-webhook-timestamp']!,
body: body,
header: headers['dartvel-webhook-signature']!,
tolerance: const Duration(minutes: 5),
);Copy code to clipboard
With signature: standard, your customers can verify with the official standardwebhooks library for their language. In Dart:
// With signature: standard, any official standardwebhooks library verifies a
// delivery. In Dart, so does this; the key is the whsec_ value.
bool isGenuineStandard(Map<String, String> headers, String body, String key) =>
DVStandardWebhookSignature.verify(
secret: key,
headers: headers,
body: body, // exactly as received, before any JSON parsing
); // refuses a timestamp more than five minutes offCopy code to clipboard
A receiver reads a CloudEvent in either mode. Verify the signature first, over the body exactly as it arrived.
// With format: cloudevents, read the event in either content mode.
String orderIdFrom(Map<String, String> headers, String body) {
final DVCloudEvent event = DVCloudEvent.fromHttp(headers: headers, body: body);
// event.id is the delivery id: deduplicate on it.
final Map<String, Object?> data = event.data! as Map<String, Object?>;
return '${event.type} ${data['orderId']}';
}Copy code to clipboard
dartvel build writes an AsyncAPI 3.0 document for your declared events, and the backend serves it at /api/asyncapi.json.
It is read from the same DVWebhookEvent declarations emit checks, so the catalog and the code list the same events.
Each message is described in the envelope and with the headers dartvel.webhooks selects.
Write each event name as a string literal. A name the build cannot read stops it with DV-WEBHOOK-010.
Partial
Spec section: Outbound Webhooks
Planned work and implementation limits
Subscriptions and events are not generated.
The envelope and signature are set for the whole app. A subscription cannot choose its own yet.
The AsyncAPI document describes the envelope. The data inside it has no schema yet.
You call drainAll. Nothing drains deliveries in the background yet.
The tenant is recorded and not enforced.
FSL-1.1-MIT licensed. Built with Dartvel.
Dartvel is made by
To the bottom