Entitlement Grant
The payload sent to your webhook endpoint when an entitlement grant is created, delivered, fails, or is revoked.
Entitlement Grant Webhook Events
These events fire whenever a customer’s entitlement grant changes state, for example when a license key is generated, a Discord role is assigned, a download link is provisioned, or access is revoked. Subscribe to these events to keep your application in sync with what each customer can access.EntitlementGrantResponse payload documented in the schema below.
Event Triggers
entitlement_grant.created
A grant row was inserted. The grant always has a stableid from this point on, even if its status changes. Use this event to record that fulfillment is in progress.
For auto-fulfilled license keys and feature flags the row is inserted directly with status: "Delivered" and delivered_at populated, so a single created event is followed by no further state changes unless the grant is later revoked.
For manually-fulfilled license keys (entitlements with fulfillment_mode: manual) the row arrives with status: "Pending" and no license_key object — there is no key yet. This event is your signal that a key is awaiting fulfillment; supply it via POST /grants/{grant_id}/license-key, which then fires license_key.created and entitlement_grant.delivered. See Manual Fulfillment.
For every other integration the row arrives with status: "Pending". A delivered or failed event follows once delivery completes:
- OAuth-based integrations (Discord, GitHub, Notion) use an
oauth_urlthe customer must visit to complete consent. Dodo Payments tries to create it when the grant is created, soentitlement_grant.createdmay include it; if it isnull, it is filled in when the customer starts the accept flow from the Customer Portal. The grant staysPendinguntil the customer authorizes. - Platform-direct integrations (Telegram, Framer, Digital Files) sit in
Pendingonly briefly while the platform call runs, then move toDelivered.
entitlement_grant.delivered
The grant transitioned toDelivered, usually from Pending. The customer now has the access described by the entitlement. Use this event to unlock dependent features in your own systems, for example to provision a workspace, send a custom welcome email, or mark a “fulfilled” flag.
The payload’s delivered_at field captures when delivery completed. delivered fires whenever an existing grant’s status changes to Delivered: from Pending, when a failed OAuth grant later succeeds, or when a revoked grant is restored. A grant that arrives Delivered on creation, such as an auto-fulfilled license key, fires created only.
entitlement_grant.failed
Delivery was attempted and failed with a non-retryable error. Theerror_code and error_message fields explain the failure. Common causes include a revoked OAuth token, a denied platform permission, or a missing target (e.g., a deleted Discord guild).
entitlement_grant.revoked
Access was withdrawn at the platform level: Discord role removed, GitHub collaborator removed, license key disabled, file download URLs no longer issued. Therevocation_reason field records the trigger.
Payload Variants
Thedata field is always an EntitlementGrantResponse object. The payload carries an integration_type field (for example license_key, digital_files, discord) so you can recognize the grant type directly. Three integration types also attach extra nested objects:
license_keyis included whenintegration_typeislicense_keyand a key has been issued. It contains the generated key, expiry, and activation usage. For a manually-fulfilled grant still inPending, this object isnulluntil you fulfill the grant.digital_product_deliveryis included whenintegration_typeisdigital_files. It contains presigned download URLs, the optionalinstructions, and the optionalexternal_url.featureis included whenintegration_typeisfeature_flag. It contains thefeature_typeandfeature_idof the capability conferred by the grant.
null; the relevant configuration is captured in the entitlement itself, not the grant.
Sample Payloads
License Key Delivered (entitlement_grant.delivered)
License Key Pending Manual Fulfillment (entitlement_grant.created)
Fired when a customer buys a product whose License Key entitlement uses fulfillment_mode: manual. The grant is Pending with no license_key object yet — the merchant must supply the key.
Digital Files Delivered (entitlement_grant.delivered)
Discord Role Created and Pending (entitlement_grant.created)
Grant Revoked on Subscription Cancellation (entitlement_grant.revoked)
Delivery Failed (entitlement_grant.failed)
Integration Tips
- Unlock dependent features when a grant reaches
Delivered. Apayment.succeededevent tells you the money cleared; it does not tell you the customer has the GitHub repo or the Discord role yet. Handleentitlement_grant.delivered, and alsoentitlement_grant.createdwithstatus: "Delivered", because a grant that is delivered on creation fires nodeliveredevent. - Map
revocation_reasonto retention flows. Asubscription_on_holdrevoke usually means the customer’s card failed and the next renewal will re-grant access. Amanualorsubscription_cancelledrevoke is intentional. Treat them differently in customer messaging. - Detect duplicates with the
webhook-idheader, not the grantid. A grant emitscreatedonce, butdeliveredandrevokedcan each fire more than once, because a revoked grant can be restored and revoked again.failedis not always final either: a failed OAuth grant can still be delivered. Re-deliveries from the webhook system can also repeat an event. Skip repeats bywebhook-id, and key your own grant records on the grantid. - Read
integration_typeto recognize the grant type. The payload carriesintegration_typedirectly (for examplelicense_key,digital_files,discord). Thelicense_keyanddigital_product_deliverynested objects are populated once their respective grants are delivered; a manually-fulfilled license-key grant staysPendingwithintegration_type: "license_key"and anulllicense_keyuntil you fulfill it. - For OAuth-based grants, surface
oauth_urlto the customer. Theentitlement_grant.createdevent for Discord, GitHub, or Notion subscriber flows may include anoauth_urlandoauth_expires_at. If it isnull, wait for a later event or direct the customer to the Customer Portal. Email the URL to the customer or display it in your app to unblock delivery.
Detailed view of a single entitlement grant: who it's for, its lifecycle state, and any integration-specific delivery payload.
Brand id this grant belongs to.
Identifier of the business that owns the grant.
Timestamp when the grant was created.
Identifier of the customer the grant was issued to.
Identifier of the entitlement this grant was issued from.
Unique identifier of the grant.
The integration type of the grant's entitlement (e.g. license_key).
discord, telegram, github, figma, framer, notion, digital_files, license_key, feature_flag Arbitrary key-value metadata recorded on the grant.
Lifecycle status of the grant.
Pending, Delivered, Failed, Revoked Timestamp when the grant was last modified.
Timestamp when the grant transitioned to delivered, when applicable.
Digital-product-delivery payload, present when the entitlement
integration is digital_files.
Machine-readable code reported when delivery failed, when applicable.
Human-readable message reported when delivery failed, when applicable.
Typed feature payload, present only when the entitlement integration is
feature_flag; null for every other integration type.
License-key delivery payload, present when the entitlement integration
is license_key.
Timestamp when oauth_url stops being valid, when applicable.
Customer-facing OAuth URL for OAuth-style integrations. Populated
during the customer-portal accept flow; null until the customer
completes that step, and on grants for non-OAuth integrations.
Identifier of the payment that triggered this grant, when applicable.
Reason recorded when the grant was revoked, when applicable.
Timestamp when the grant transitioned to revoked, when applicable.
Identifier of the subscription that triggered this grant, when applicable.