Webhooks

Webhooks call a URL that you register in Áskell when certain events happen. These are the events you can subscribe to:

Event identifier

Description

subscription.*

All events that are connected to subscriptions

subscription.created

Subscription created

subscription.changed

Subscription changed

subscription.renewed

Subscription renewed

subscription_contract.*

All events related to subscription contracts

subscription_contract.created

Subscription contract created

subscription_contract.changed

Subscription contract changed

subscription_contract.renewed

Subscription contract renewed

subscription_contract.migrated

Legacy subscription migrated to a subscription contract

subscription_contract.activated

Subscription contract activated

subscription_contract.paused

Subscription contract paused

subscription_contract.resumed

Subscription contract resumed after a pause

subscription_contract.canceled

Subscription contract canceled immediately

subscription_contract.ended

Subscription contract ended

subscription_contract_item.*

All events related to contract items

subscription_contract_item.entitlement_changed

Contract item entitlements changed (not covered by subscription_contract.*)

subscription_contract_scheduled_change.*

All events related to scheduled contract item changes (not covered by subscription_contract.*)

subscription_contract_scheduled_change.created

Item change scheduled for the next renewal

subscription_contract_scheduled_change.applied

Scheduled item change applied at renewal

subscription_contract_scheduled_change.canceled

Scheduled item change canceled

billing_run.*

All events related to billing runs

billing_run.created

Billing run created

billing_run.changed

Billing run changed

billing_run.succeeded

Billing run succeeded

billing_run.failed

Billing run failed permanently

billing_run.retry

Billing run retry scheduled

fulfillment_order.*

All events related to fulfillment orders

fulfillment_order.created

Fulfillment order created

fulfillment_order.fulfilled

Fulfillment order fulfilled

fulfillment_order.cancelled

Fulfillment order cancelled

fulfillment_order.shipment_booked

Shipment booked with the carrier (includes the tracking number)

customer.*

All events that are connected to customers

customer.created

Customer created

customer.changed

Customer changed

payment.*

All events related to payments (individual payments and subscription contract charges)

payment.created

Payment created

payment.changed

Payment changed (e.g. the charge succeeds or fails)

payment.retry

Payment retry scheduled

checkout.*

All events that are connected to checkouts

checkout.created

Checkout created

checkout.changed

Checkout changed

Identification

We recommend that all webhooks are authenticated. We sign each call with a header named Hook-HMAC. Its value is an HMAC digest built from the body of the call and a secret known only to us and you. You receive this secret when you create a new webhook. The event type is also sent in the Hook-Event header. The API data version is sent in the Hook-API-Version header. Legacy plan/subscription webhooks send v1, while the new subscription contract system sends v2.

Here is a Python example. The process is very similar to what Shopify uses and you can find more code examples in their documentation here.

  • python
  • ruby
  • php
import base64
import hmac
import hashlib

WEBHOOK_SECRET = "Your webhook secret".encode()
WEBHOOK_DIGEST_TYPE = 'sha512'

def verify(hmac_header, digest_method, secret, message):
    digestmod = getattr(hashlib, digest_method)
    signed = base64.b64encode(
        hmac.new(secret, message, digestmod).digest(),
    ).strip()
    return hmac.compare_digest(signed, hmac_header)

# your view function
def handle_webhook(request):
    # The signature
    digest = request.META.get('HTTP_HOOK_HMAC').encode()
    # The name of the webhook event
    event = request.META.get('HTTP_HOOK_EVENT').encode()

    body = request.body
    if verify(digest, WEBHOOK_DIGEST_TYPE, WEBHOOK_SECRET, body):
        payload = json.loads(body)
        # ... the rest of your code here```
require 'rubygems'
require 'base64'
require 'openssl'
require 'sinatra'

WEBHOOK_SECRET = 'Your webhook secret'
WEBHOOK_DIGEST_TYPE = 'sha512'

helpers do
    def verify_webhook(secret, data, hmac_header)
        digested = ::OpenSSL::HMAC.digest(WEBHOOK_DIGEST_TYPE, secret, data)
        calculated_hmac = Base64.encode64(digested).strip
        return calculated_hmac == hmac_header
    end
end

post '/' do
    request.body.rewind
    data = request.body.read
    if verify_webhook(WEBHOOK_SECRET, env["HTTP_HOOK_HMAC"])
        # deserialize data' using json and process webhook
    end
end
<?php

define('WEBHOOK_SECRET', 'Your webhook secret')
define('WEBHOOK_DIGEST_TYPE', 'sha512')

function verify_webhook($data, $hmac_header)
{
    $calculated_hmac = base64_encode(hash_hmac(WEBHOOK_DIGEST_TYPE, $data,
        WEBHOOK_SECRET, true));
    return ($hmac_header == $calculated_hmac);
}

$hmac_header = $_SERVER['HTTP_HOOK_HMAC'];
$data = file_get_contents('php://input');
$verified = verify_webhook($data, $hmac_header);

?>

Subscription contracts and migration from the legacy subscription system

V2 subscription contracts use subscription_contract.* events. When a legacy subscription is migrated to V2, a subscription_contract.migrated event is sent. It means that the V2 contract is the new billing and access authority for the relevant subscription.

During migration, legacy subscription.* aliases are not sent for the V2 contract because the event data is based on SubscriptionContract, not the legacy Subscription object. For the same reason, a subscription.canceled event is not sent solely because billing moves from a legacy subscription to a V2 contract.

Data in webhooks

The body of every call is a JSON object with four fields:

{
    "event": "subscription_contract.created",
    "ref": null,
    "sender": "notandanafn",
    "data": {}
}

event is the name of the event (the same value as in the Hook-Event header), sender is the username of the account that sends the event, and ref is unused and always null. The payload itself is in data. The examples below show the contents of data for each group of events.

Subscriptions (subscription.*)

When a subscription is created, updated or renewed the following events are sent. Here is an example of data that comes with a subscription event:

{
    "id": 12345,
    "plan": {
        "id": 12345,
        "name": "Titill",
        "alternative_name": "Annar titill",
        "reference": "",
        "interval": "month",
        "interval_count": 1,
        "amount": "1.0000",
        "currency": "ISK",
        "trial_period_days": 5,
        "enabled": true,
        "private": false,
        "electronic_only": true,
        "description": "Some description"
    },
    "customer": 12345,
    "customer_reference": "customer-12345",
    "trial_end": "2024-01-01T00:00:00Z",
    "start_date": "2024-01-01T00:00:00Z",
    "ended_at": null,
    "reference": "reference-12345",
    "active": true,
    "meta": "{}",
    "description": "Some subscription description",
    "active_until": "2024-02-01T00:00:00Z",
    "is_on_trial": false,
    "token": "md5sumtokenwith32chars",
    "is_failing": false,
    "last_billing_log": {
        "id": 12345,
        "billing_log_type": "renewal",
        "billing_date": "2024-01-01T00:00:00Z",
        "plan_billed_up_to": "2024-02-01T00:00:00Z",
        "total": null,
        "transaction": {
        "external_reference": null,
        "data": {},
        "state": "settled",
        "uuid": "some-guid-with-numbers-and-letters",
        "fail_code": null,
        "refund_code": null,
        "cancel_code": null,
        "amount": "1.00",
        "currency": "ISK",
        "payment_method": null,
        "created_at": "2024-01-01T00:00:00Z"
        }
    },
    "delivery_address": null,
    "amount": "1.0000"
    }

Subscription contracts (subscription_contract.*)

The events are sent in the following situations:

Event

When

subscription_contract.created

A subscription contract is created.

subscription_contract.changed

A subscription contract is saved with changes, e.g. when a cancellation is scheduled for the end of the period.

subscription_contract.renewed

Billing for a new period has succeeded and the subscription contract moves on to it.

subscription_contract.migrated

A legacy subscription has been migrated to a subscription contract. See the payload below.

subscription_contract.activated

A subscription contract becomes active, e.g. when the first payment succeeds or when a canceled subscription contract is restarted.

subscription_contract.paused

A pause takes effect.

subscription_contract.resumed

A pause ends and the subscription contract is active again.

subscription_contract.canceled

A subscription contract is canceled immediately.

subscription_contract.ended

A subscription contract ends, either because a scheduled cancellation takes effect or because a one-time purchase contract concludes.

subscription_contract.changed is sent every time a subscription contract is saved, so it often arrives alongside other events in the table. Note that when a scheduled cancellation takes effect, subscription_contract.ended is sent, not subscription_contract.canceled, even though the subscription contract’s state becomes canceled. subscription_contract.canceled is only sent when a subscription contract is canceled immediately.

Events about contract item entitlements (subscription_contract_item.*) and scheduled item changes (subscription_contract_scheduled_change.*) do not send the subscription contract and are therefore not covered by subscription_contract.*. A webhook has to be registered for them specifically, see Contract item entitlements (subscription_contract_item.*) and Scheduled item changes (subscription_contract_scheduled_change.*).

All subscription_contract.* events except subscription_contract.migrated send the subscription contract in the same shape as the V2 API returns it, see Fields in the contract response. Here is an example of the data sent with a subscription contract event:

{
    "id": 12345,
    "customer_id": 33482,
    "customer": {
        "id": 33482,
        "customer_reference": "customer-12345",
        "email": "jon@example.com",
        "customer_username": "jon"
    },
    "customer_reference": "customer-12345",
    "reference": null,
    "delivery_address": null,
    "shipping_selection": null,
    "state": "active",
    "billing_anchor_at": "2024-01-01T00:00:00Z",
    "billing_timezone": "Atlantic/Reykjavik",
    "billing_time": "00:00:00",
    "billing_advance_policy": "on_run_succeeded",
    "service_entitlement_policy": "on_run_succeeded",
    "service_active": true,
    "service_state": "active",
    "active_service_item_count": 1,
    "inactive_service_item_count": 0,
    "total_active_quantity": 1,
    "entitlement_summary": {
        "service_active": true,
        "service_state": "active",
        "active_service_item_count": 1,
        "inactive_service_item_count": 0,
        "past_due_service_item_count": 0,
        "total_active_quantity": 1,
        "active_product_ids": [
            222
        ],
        "evaluated_at": "2024-01-01T00:00:00Z"
    },
    "billing_attention_required": false,
    "trial_start_at": null,
    "trial_end_at": null,
    "next_billing_at": "2024-02-01T00:00:00Z",
    "cancel_at": null,
    "cancel_at_period_end": false,
    "canceled_at": null,
    "ended_at": null,
    "currency": "ISK",
    "recurring": true,
    "contract_version": 1,
    "default_proration_behavior": "create_prorations",
    "electronic_only": true,
    "has_future_pause": false,
    "paused_until": null,
    "payment_processor_override_id": null,
    "accounting_department": null,
    "accounting_cost_center": null,
    "legacy_subscription_ids": [
        9876
    ],
    "legacy_source_mappings": [
        {
            "legacy_subscription_id": 9876,
            "subscription_contract_item_id": 23456,
            "role": "item_source",
            "migration_effective_at": "2024-01-01T00:00:00Z"
        }
    ],
    "metadata": {},
    "subscriber_page": "https://askell.is/change_contract/<token>/",
    "created_at": "2024-01-01T00:00:00Z",
    "updated_at": "2024-01-01T00:00:00Z",
    "items": [
        {
            "id": 23456,
            "price_id": 333,
            "product": {
                "id": 222,
                "name": "Mánaðaráskrift",
                "reference": "manadaraskrift",
                "image_url": null
            },
            "price": {
                "id": 333,
                "product_id": 222,
                "product_name": "Mánaðaráskrift",
                "product_reference": "manadaraskrift",
                "currency": "ISK",
                "currency_format": "{amount} kr",
                "unit_amount": "2990.0000",
                "description": "",
                "billing_type": "recurring",
                "recurrence_type": "interval",
                "interval": "month",
                "interval_count": 1,
                "billing_day_of_month": null,
                "billing_schedule_id": null,
                "billing_schedule_name": null,
                "billing_schedule_timezone": null,
                "billing_schedule_rules": [],
                "proration_policy": "prorate_by_time",
                "initial_billing_policy": "next_scheduled_period",
                "next_occurrences": [],
                "active": true,
                "current_version_id": 444,
                "versions": [
                    {
                        "id": 444,
                        "unit_amount": "2990.0000",
                        "valid_from": null,
                        "valid_to": null,
                        "active": true,
                        "metadata": {},
                        "created_at": "2024-01-01T00:00:00Z",
                        "updated_at": "2024-01-01T00:00:00Z"
                    }
                ],
                "currently_effective": true,
                "effective_from": null,
                "effective_to": null,
                "metadata": {},
                "created_at": "2024-01-01T00:00:00Z",
                "updated_at": "2024-01-01T00:00:00Z"
            },
            "quantity": 1,
            "unit_amount_override": null,
            "discount_percent": null,
            "active": true,
            "start_at": "2024-01-01T00:00:00Z",
            "end_at": null,
            "billing_anchor_at": "2024-01-01T00:00:00Z",
            "next_billing_date": null,
            "next_billing_at": "2024-02-01T00:00:00Z",
            "last_billed_at": "2024-01-01T00:00:00Z",
            "service_active": true,
            "service_state": "active",
            "entitled_from": "2024-01-01T00:00:00Z",
            "entitled_until": "2024-02-01T00:00:00Z",
            "current_service_period_start_at": "2024-01-01T00:00:00Z",
            "current_service_period_end_at": "2024-02-01T00:00:00Z",
            "entitlement_billing_run_id": 54321,
            "metadata": {}
        }
    ],
    "pauses": [],
    "current_pauses": [],
    "future_pauses": [],
    "past_pauses": [],
    "billing_cadence": {
        "recurrence_type": "interval",
        "interval": "month",
        "interval_count": 1,
        "billing_day_of_month": null,
        "billing_schedule_id": null,
        "billing_schedule_name": null
    },
    "bundle_purchases": [],
    "bundle_purchase_id": null,
    "initial_billing_run": {
        "id": 54321,
        "state": "succeeded",
        "run_type": "recurring",
        "period_start_at": "2024-01-01T00:00:00Z",
        "period_end_at": "2024-02-01T00:00:00Z",
        "billing_date": "2024-01-01",
        "due_at": "2024-01-01T00:00:00Z",
        "currency": "ISK",
        "subtotal_amount": "2990.0000",
        "tax_amount": "0.0000",
        "total_amount": "2990.0000",
        "attempt_count": 1,
        "max_attempts": 5,
        "next_retry_at": null,
        "last_attempt_at": "2024-01-01T00:00:00Z",
        "transaction_id": 98765,
        "metadata": {},
        "created_at": "2024-01-01T00:00:00Z",
        "updated_at": "2024-01-01T00:00:00Z"
    },
    "initial_billing_run_id": 54321,
    "initial_billing_run_total_amount": "2990.0000",
    "latest_billing_run": {
        "id": 54321,
        "state": "succeeded",
        "run_type": "recurring",
        "period_start_at": "2024-01-01T00:00:00Z",
        "period_end_at": "2024-02-01T00:00:00Z",
        "billing_date": "2024-01-01",
        "due_at": "2024-01-01T00:00:00Z",
        "currency": "ISK",
        "subtotal_amount": "2990.0000",
        "tax_amount": "0.0000",
        "total_amount": "2990.0000",
        "attempt_count": 1,
        "max_attempts": 5,
        "next_retry_at": null,
        "last_attempt_at": "2024-01-01T00:00:00Z",
        "transaction_id": 98765,
        "metadata": {},
        "created_at": "2024-01-01T00:00:00Z",
        "updated_at": "2024-01-01T00:00:00Z"
    },
    "discount": null
}

customer is an object with details about the customer, while the customer’s ID is in customer_id. service_active and service_state tell whether service should be provided right now.

reference is the subscription contract’s reference in an external system and metadata the integration’s metadata, including metadata from a checkout and where the subscription contract came from in askell_source. Both are set when the subscription contract is created, so they are already included in subscription_contract.created. See Contract reference and metadata.

legacy_subscription_ids and legacy_source_mappings link the subscription contract to the legacy subscriptions that were migrated to it. Both lists are empty if no legacy subscription was migrated. Each entry in legacy_source_mappings tells which legacy subscription (legacy_subscription_id) became which contract item (subscription_contract_item_id), its role in the migration (role: primary, item_source or history_only) and when the migration took effect (migration_effective_at).

Migration from the legacy subscription system (subscription_contract.migrated)

subscription_contract.migrated does not send the whole subscription contract but a shorter payload about the migration:

{
    "id": 12345,
    "subscription_contract_id": 12345,
    "migration_batch_id": 77,
    "customer": 33482,
    "customer_reference": "customer-12345",
    "billing_managed_by": "subscription_contract",
    "migration_effective_at": "2024-01-01T00:00:00+00:00",
    "legacy_subscription_ids": [
        9876
    ],
    "legacy_source_mappings": [
        {
            "legacy_subscription_id": 9876,
            "subscription_contract_item_id": 23456,
            "role": "item_source",
            "migration_effective_at": "2024-01-01T00:00:00+00:00"
        }
    ]
}

Field

Description

id / subscription_contract_id

ID of the subscription contract. Both fields have the same value.

migration_batch_id

ID of the migration batch that migrated the subscription.

customer

ID of the customer as a number. Unlike the other subscription_contract.* events, customer is not an object here.

customer_reference

Customer reference.

billing_managed_by

Always subscription_contract. From now on, billing happens through the subscription contract rather than the legacy subscription.

migration_effective_at

The earliest effective time in legacy_source_mappings.

legacy_subscription_ids

IDs of the legacy subscriptions that were migrated to the subscription contract.

legacy_source_mappings

Same shape as in the other subscription_contract.* events.

Billing runs (billing_run.*)

When a billing run is created, changes, succeeds, fails or is scheduled for a retry, the corresponding events are sent. Here is an example of the data sent with a billing run event:

{
    "id": 54321,
    "contract_id": 12345,
    "source_contract_change_id": null,
    "run_type": "recurring",
    "customer_id": 33482,
    "customer_reference": "customer-12345",
    "period_start_at": "2024-01-01T00:00:00Z",
    "period_end_at": "2024-02-01T00:00:00Z",
    "billing_date": "2024-01-01",
    "due_at": "2024-01-01T00:00:00Z",
    "state": "succeeded",
    "currency": "ISK",
    "subtotal_amount": "2990.0000",
    "tax_amount": "0.0000",
    "total_amount": "2990.0000",
    "attempt_count": 1,
    "max_attempts": 5,
    "next_retry_at": null,
    "last_attempt_at": "2024-01-01T00:00:00Z",
    "transaction_id": 98765,
    "transaction_uuid": "3f1c2a9e-5b7d-4c8e-9a6f-2d4b8e1c7a90",
    "transaction_external_reference": "410009982476",
    "lines": [
        {
            "id": 76543,
            "contract_id": 12345,
            "line_type": "recurring",
            "contract_item_id": 23456,
            "price_id": 333,
            "price_version_id": null,
            "product_name": "Mánaðaráskrift",
            "product_reference": "manadaraskrift",
            "quantity": 1,
            "unit_amount": "2990.0000",
            "discount_percent": null,
            "currency": "ISK",
            "line_subtotal_amount": "2990.0000",
            "line_tax_amount": "0.0000",
            "line_total_amount": "2990.0000",
            "service_period_start_at": "2024-01-01T00:00:00Z",
            "service_period_end_at": "2024-02-01T00:00:00Z",
            "proration_factor": "1.000000",
            "proration_reason": null,
            "proration_behavior": null,
            "settlement_behavior": null,
            "change_effective_at": null,
            "adjustment_service_start_at": null,
            "adjustment_service_end_at": null,
            "source_billing_run_line_id": null,
            "source_contract_change_id": null,
            "imported_billing_run_id": null,
            "target_contract_item_id": null,
            "target_billing_date": null,
            "target_due_at": null,
            "tax_rate_id": null,
            "tax_rate_version_id": null,
            "tax_percent": null,
            "tax_calculation_mode": null,
            "metadata": {},
            "created_at": "2024-01-01T00:00:00Z",
            "updated_at": "2024-01-01T00:00:00Z"
        }
    ],
    "attempts": [
        {
            "id": 65432,
            "attempt_no": 1,
            "state": "succeeded",
            "idempotency_key": "v2-billing-run-54321-attempt-1",
            "transaction_id": 98765,
            "transaction_uuid": "3f1c2a9e-5b7d-4c8e-9a6f-2d4b8e1c7a90",
            "transaction_external_reference": "410009982476",
            "fail_code": null,
            "fail_message": null,
            "started_at": "2024-01-01T00:00:00Z",
            "finished_at": "2024-01-01T00:00:00Z",
            "metadata": {},
            "created_at": "2024-01-01T00:00:00Z",
            "updated_at": "2024-01-01T00:00:00Z"
        }
    ],
    "metadata": {},
    "created_at": "2024-01-01T00:00:00Z",
    "updated_at": "2024-01-01T00:00:00Z"
}

contract_id is the ID of the subscription contract. transaction_id, transaction_uuid and transaction_external_reference point to the transaction that charged the billing run and are null if no transaction has been created. lines are the lines of the billing run and attempts the charge attempts. state is scheduled, processing, retry_scheduled, pending_external, succeeded, failed_terminal, canceled, voided or refunded.

Contract item entitlements (subscription_contract_item.*)

subscription_contract_item.entitlement_changed is sent when a contract item’s entitlements change: whether service should be provided (service_active / service_state), until when (entitled_until), the quantity or the item’s product. This happens, for example, when a billing run succeeds and grants entitlements for a new period, when a trial starts, when a subscription contract is paused, resumed, canceled or ends, and when a contract item is changed.

Important

This event is not covered by subscription_contract.*. To receive it, register a webhook specifically for subscription_contract_item.entitlement_changed or subscription_contract_item.*.

{
    "id": 23456,
    "subscription_contract_id": 12345,
    "contract_item_id": 23456,
    "customer": 33482,
    "customer_reference": "customer-12345",
    "product_id": 222,
    "product_reference": "manadaraskrift",
    "price_id": 333,
    "quantity": 1,
    "service_active": true,
    "service_state": "active",
    "entitled_from": "2024-01-01T00:00:00+00:00",
    "entitled_until": "2024-02-01T00:00:00+00:00",
    "current_service_period_start_at": "2024-01-01T00:00:00+00:00",
    "current_service_period_end_at": "2024-02-01T00:00:00+00:00",
    "entitlement_billing_run_id": 54321,
    "changed_fields": [
        "entitled_until",
        "service_active",
        "service_state"
    ],
    "previous": {
        "service_active": false,
        "service_state": "inactive",
        "entitled_until": null,
        "quantity": 1,
        "product_id": 222,
        "product_reference": "manadaraskrift"
    }
}

id and contract_item_id are the ID of the contract item. changed_fields lists the fields that changed, and previous holds the entitlement fields (service_active, service_state, entitled_until, quantity, product_id and product_reference) as they were before the change. entitlement_billing_run_id is the billing run that granted the entitlements, if any.

Scheduled item changes (subscription_contract_scheduled_change.*)

These events follow item changes that are scheduled for the next renewal (apply_at=period_end), see Scheduled item changes (apply_at=period_end). They are sent in the following situations:

Event

When

subscription_contract_scheduled_change.created

An item change is scheduled for the next renewal.

subscription_contract_scheduled_change.applied

The billing run for the renewal is created and the change has been applied to the item.

subscription_contract_scheduled_change.canceled

A scheduled change is canceled, or is canceled automatically because the item is removed or the subscription contract is canceled.

Important

These events are not covered by subscription_contract.*. To receive them, register a webhook specifically for the individual events or for subscription_contract_scheduled_change.*.

The payload is the scheduled change in the same shape as it appears in scheduled_changes in the V2 API, together with the IDs of the subscription contract and the customer. Here is an example of the data sent with subscription_contract_scheduled_change.created:

{
    "id": 8765,
    "contract_item": 23456,
    "operation": "update_item",
    "state": "scheduled",
    "scheduled_for": "2024-02-01T00:00:00Z",
    "price_id": 334,
    "price": {
        "id": 334,
        "product_id": 223,
        "product_name": "Mánaðaráskrift Plus",
        "product_reference": "manadaraskrift-plus",
        "currency": "ISK",
        "currency_format": "{amount} kr",
        "unit_amount": "1990.0000",
        "description": "",
        "billing_type": "recurring",
        "recurrence_type": "interval",
        "interval": "month",
        "interval_count": 1,
        "billing_day_of_month": null,
        "billing_schedule_id": null,
        "billing_schedule_name": null,
        "billing_schedule_timezone": null,
        "billing_schedule_rules": [],
        "proration_policy": "prorate_by_time",
        "initial_billing_policy": "next_scheduled_period",
        "next_occurrences": [],
        "active": true,
        "current_version_id": 445,
        "versions": [
            {
                "id": 445,
                "unit_amount": "1990.0000",
                "valid_from": null,
                "valid_to": null,
                "active": true,
                "metadata": {},
                "created_at": "2024-01-01T00:00:00Z",
                "updated_at": "2024-01-01T00:00:00Z"
            }
        ],
        "currently_effective": true,
        "effective_from": null,
        "effective_to": null,
        "metadata": {},
        "created_at": "2024-01-01T00:00:00Z",
        "updated_at": "2024-01-01T00:00:00Z"
    },
    "product": {
        "id": 223,
        "name": "Mánaðaráskrift Plus",
        "reference": "manadaraskrift-plus"
    },
    "quantity": 1,
    "discount_percent": null,
    "unit_amount_override": null,
    "from_price_id": 333,
    "from_quantity": 1,
    "reason": null,
    "applied_at": null,
    "canceled_at": null,
    "applied_change_id": null,
    "billing_run_id": null,
    "created_at": "2024-01-15T12:00:00Z",
    "updated_at": "2024-01-15T12:00:00Z",
    "subscription_contract_id": 12345,
    "customer": 33482,
    "customer_reference": "customer-12345"
}

Field

Description

id

ID of the scheduled change, not of the subscription contract.

subscription_contract_id

The subscription contract the change belongs to.

contract_item

ID of the contract item that changes.

operation

Always update_item.

state

scheduled in created, applied in applied and canceled in canceled.

scheduled_for

The renewal at which the change takes effect.

price_id / price / product

The price the item switches to at the renewal, in the same shape as price on a contract item, and the product it belongs to.

quantity / discount_percent / unit_amount_override

The quantity, discount and unit amount override the item switches to at the renewal.

from_price_id / from_quantity

The item’s price and quantity when the change was scheduled.

reason

Optional reason given when the change was scheduled.

applied_at / applied_change_id / billing_run_id

When the change was applied, the change recorded in the subscription contract’s history and the billing run for the renewal. null except in applied.

canceled_at

When the change was canceled. null except in canceled.

created_at / updated_at

When the scheduled change was created and last updated.

customer / customer_reference

ID of the customer as a number, and the customer reference.

Pending changes are also included with the subscription contract itself, in scheduled_changes and in scheduled_change on the item concerned, in the subscription_contract.* events that send the subscription contract.

Fulfillment orders (fulfillment_order.*)

When a physical product is paid for, a fulfillment order is created that the seller has to pick, pack and ship. The events follow it through the process: fulfillment_order.created when it is created (after the payment has succeeded), fulfillment_order.shipment_booked when a shipment is booked with the carrier and a tracking number is available, fulfillment_order.fulfilled when the order counts as fulfilled and fulfillment_order.cancelled if it is cancelled.

All four carry the same payload, i.e. the whole order as it stands when the event is sent. fulfillment_order.shipment_booked additionally includes shipment_id, which points to the shipment in fulfillments that was being booked.

{
    "id": 12345,
    "number": 42,
    "status": "open",
    "contract_id": 12345,
    "billing_run_id": 54321,
    "customer_id": 33482,
    "customer_reference": "customer-12345",
    "customer_name": "Jón Jónsson",
    "customer_email": "jon@example.com",
    "delivery_address": {
        "id": 9876,
        "delivery_name": "Jón Jónsson",
        "address_1": "Hringbraut 12",
        "address_2": "",
        "address_3": "",
        "zip_code": "101",
        "city": "Reykjavík",
        "state": null,
        "country": "IS"
    },
    "shipping_selection": {
        "id": 555,
        "option_id": 7,
        "handler": "posturinn",
        "delivery_mode": "pickup_point",
        "option_name": "Póstbox",
        "service_code": "DPO",
        "price_amount": "940.0000",
        "currency": "ISK",
        "location_id": "9591",
        "location_name": "Póstbox Hallveigarstíg",
        "location_address": "Hallveigarstíg 1 101 Reykjavík",
        "location": {
            "id": "9591",
            "name": "Póstbox Hallveigarstíg",
            "address": "Hallveigarstíg 1 101 Reykjavík",
            "street": "Hallveigarstíg 1",
            "zip_code": "101",
            "town": "Reykjavík",
            "external_id": null,
            "latitude": 64.1,
            "longitude": -21.9
        }
    },
    "lines": [
        {
            "id": 1111,
            "product_id": 222,
            "product_reference": "SKU-1",
            "product_name": "Kaffipoki",
            "quantity": 2,
            "quantity_fulfilled": 0
        }
    ],
    "fulfillments": [
        {
            "id": 333,
            "order_id": 12345,
            "handler": "posturinn",
            "status": "booked",
            "provider_order_id": "PP123456789",
            "tracking_number": "PP123456789",
            "weight_grams": 1000,
            "error": "",
            "booked_at": "2024-01-01T00:00:00Z",
            "created_at": "2024-01-01T00:00:00Z",
            "updated_at": "2024-01-01T00:00:00Z"
        }
    ],
    "estimated_weight_grams": 1000,
    "metadata": {},
    "created_at": "2024-01-01T00:00:00Z",
    "updated_at": "2024-01-01T00:00:00Z"
}

A seller who sells physical products but has no carrier set up receives orders without a shipping selection: shipping_selection is then null and fulfillments an empty list. The fulfillment_order.created and fulfillment_order.fulfilled events are still sent — the order has to be picked and fulfilled whether or not Áskell books the shipment.

Customer (customer.*)

When a customer is created or updated the following events are sent. Here is an example of data that comes with a customer event:

{
    "id": 33482,
    "first_name": "",
    "last_name": "",
    "delivery_name": "",
    "address_1": "",
    "address_2": "",
    "address_3": "",
    "zip_code": "",
    "city": "",
    "country": "",
    "email": null,
    "customer_reference": "0208912039",
    "payment_method": [],
    "address": null
}

Payments (payment.*)

payment.* events arrive with one of two payloads, depending on where the payment comes from:

  • Individual payments created through Individual payments send the legacy shape, with a list of transactions in transactions. The Hook-API-Version header is then v1.

  • Subscription contract charges, i.e. payments created by a billing run, send a flat shape that describes a single transaction directly in the payload. The Hook-API-Version header is then v2.

A webhook registered for payment.*, or for an individual payment event, receives both. To tell them apart, read the Hook-API-Version header, or check whether the subscription_contract_id field is in the payload. It is only present in the shape for subscription contract charges.

Individual payments (Hook-API-Version: v1)

When an individual payment is created, changed or retried, the corresponding events are sent. Here is an example of the data sent with an individual payment event:

{
    "uuid": "some-guid-with-numbers-and-letters",
    "amount": "1.0000",
    "currency": "ISK",
    "description": "Description here",
    "reference": "some-reference",
    "state": "settled",
    "created_at": "2024-01-01T00:00:00Z",
    "updated_at": "2024-01-01T00:00:00Z",
    "transactions": [
        {
            "external_reference": "410009982476",
            "data": {
                "id": "4100000000",
                "receipt": {}, // receipt contains vendor specific fields, not listed here
                "status": "settled"
            },
            "state": "settled",
            "uuid": "some-guid-with-numbers-and-letters",
            "fail_code": null,
            "refund_code": null,
            "cancel_code": null,
            "amount": "1.00",
            "currency": "ISK",
            "payment_method": {
                "verified": true,
                "canceled": false,
                "valid_until": "2025-03-01T00:00:00Z",
                "display_info": "XXXX-XXXX-XXXX-4324 (Visa)",
                "credit_card": true
            },
            "created_at": "2024-01-01T00:00:00Z"
        }
    ]
}

Subscription contract charges (Hook-API-Version: v2)

A billing run sends payment.created when a transaction is created for a charge attempt, payment.changed when the charge succeeds, fails permanently or awaits confirmation from the payment processor, and payment.retry when a new attempt is scheduled. Here is an example of the data sent with a subscription contract charge event:

{
    "uuid": "3f1c2a9e-5b7d-4c8e-9a6f-2d4b8e1c7a90",
    "amount": "2990.00",
    "currency": "ISK",
    "description": "Billing run 54321",
    "reference": "410009982476",
    "state": "settled",
    "created_at": "2024-01-01T00:00:00+00:00",
    "updated_at": "2024-01-01T00:00:00+00:00",
    "fail_code": null,
    "refund_code": null,
    "cancel_code": null,
    "payment_method": 1111,
    "billing_run_id": 54321,
    "billing_run_attempt_id": 65432,
    "subscription_contract_id": 12345,
    "customer": 33482,
    "customer_reference": "customer-12345",
    "legacy_subscription_ids": [
        9876
    ]
}

Field

Description

uuid

ID of the transaction.

amount / currency

Amount and currency of the transaction.

description

Always Billing run <billing run ID>.

reference

The payment processor’s reference for the transaction.

state

State of the transaction: initial, pending, settled, failed, canceled or refunded.

created_at / updated_at

When the transaction was created and last updated.

fail_code / refund_code / cancel_code

The transaction’s codes if the charge failed, was refunded or was canceled.

payment_method

ID of the payment method as a number, not an object as in the legacy shape.

billing_run_id / billing_run_attempt_id

The billing run that created the payment, and the attempt within it.

subscription_contract_id

The subscription contract being billed.

customer / customer_reference

ID of the customer as a number, and the customer reference.

legacy_subscription_ids

Legacy subscriptions that were migrated to the subscription contract.

A payment.retry may carry no transaction, e.g. if the attempt failed before a transaction was created. The transaction fields (uuid, reference, created_at, updated_at, payment_method and the codes) are then null, amount and currency come from the billing run, and state is the billing run’s state, e.g. retry_scheduled, rather than a transaction state.

Checkout (checkout.*)

When a checkout is created or changed:

{
    "token": "3a44383eb19dc9183be4df5ad655094c88159af4",
    "checkout_url": "http://askell.is/checkout/3a44383eb19dc9183be4df5ad655094c88159af4/",
    "status": "tokencreated"
}