Dartvel, home
Docs
Features
Studio
Cloud
Compared

Search the site

GitHub
pub.dev

GETTING STARTED

Getting started
Existing Flutter apps
Existing Native apps
Run on your phone

APP

UI and styling
Routing
State
Accessibility
Keyboard shortcuts
Localization
Devices and desktop
Native device access
Media, 3D and XR

DATA

Data models
Forms
Search
Sync and offline
Import and export
Change capture
Database
Cache
File storage
Images
Privacy and erasure

BACKEND

Backend functions
Auth and sessions
Authorization
Queues and jobs
Workers and memory
Notifications and mail
Outbound HTTP
AI
Webhooks
GraphQL and OpenAPI
API keys and OAuth
Multi-tenancy
Billing and commerce
Modules

OPERATIONS

Edge security
Secrets and environments
Monitoring
Releases

SHIPPING

Build targets
Telegram Mini Apps
Static web hosting
Servers and deploying

REFERENCE

Testing
CLI reference
Coding agents

BACKEND

Webhooks

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

Declare, subscribe and emit

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.

Deliver and retry

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.

Choose the envelope and the signature

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) | standard

Copy 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.

Verify a delivery on the receiving side

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 off

Copy 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

Publish the event catalog

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.

Status

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.

PREVIOUS AI Chat, structured output, embeddings and tools
NEXT GraphQL and OpenAPI The API your models and functions already have
GitHub
pub.dev
npm
Acknowledgements
Privacy
Terms

FSL-1.1-MIT licensed. Built with Dartvel.

Dartvel is made by

SigmaDev Digital

To the bottom