# Callback Details

Callbacks notify your application when an asynchronous DIDWW API operation changes state.
Configure a callback on the individual resource and DIDWW sends a signed HTTP request to
the specified URL when a supported event occurs.

## Supported callbacks

Use the callback that belongs to the resource whose state you need to monitor.

| Resource | Configure with | Callback event | Delivery |
| --- | --- | --- | --- |
| [Orders](inventory-resources/order/index.html) | [Create Order](inventory-resources/order/create-order.html) | The Order becomes `completed` or `canceled`. | `get` or `post` |
| [Exports](export/index.html) | [Create Export](export/create-export.html) | Export processing is completed. | `get` or `post` |
| [Address Verifications](regulation-resources/address-verifications/index.html) | [Create Address Verification](regulation-resources/address-verifications/create-address-verification.html) | The verification becomes `approved` or `rejected`. | `get` or `post` |
| [Emergency Verifications](emergency-resources/emergency-verifications/index.html) | [Create Emergency Verification](emergency-resources/emergency-verifications/create-emergency-verification.html) | The verification becomes `approved` or `rejected`. | `get` or `post` |
| [Outbound Trunks](inventory-resources/voice-out-trunks/index.html) | [Create](inventory-resources/voice-out-trunks/create-voice-out-trunk.html) or [update](inventory-resources/voice-out-trunks/update-voice-out-trunk.html) an Outbound Trunk | The trunk is blocked after reaching its 24-hour threshold or is unblocked. | `post` with a JSON array |

## Configure a callback

1. Enable callbacks on one API key in your DIDWW account. DIDWW uses this key to sign
   callback requests. Only one API key can have `enable_callbacks` enabled.
2. Set `callback_url` to a valid HTTP or HTTPS URL when creating the resource. For
   Orders, Exports, Address Verifications, and Emergency Verifications, also set
   `callback_method` to `get` or `post`.
3. Use an HTTPS callback URL so the request and its signature are protected in transit.
4. Make the receiving endpoint return a `2xx` response within 60 seconds.

Important

If no API key has `enable_callbacks` enabled, DIDWW does not send callbacks.
Use the enabled key as the HMAC secret when validating the
`X-DIDWW-Signature` header.

A callback is configured on an individual resource rather than through a separate callback
endpoint. Address Verification and Emergency Verification callbacks are set at creation
time. An Outbound Trunk's `callback_url` can also be changed with the update endpoint.

## Delivery formats

For Orders, Exports, Address Verifications, and Emergency Verifications:

- A `get` callback sends the payload as query parameters.
- A `post` callback sends the payload in the request body with
  `Content-Type: application/x-www-form-urlencoded`.

Outbound Trunk callbacks always use `post`. They send
`Content-Type: application/json` and a JSON array containing one or more event
objects.

Verify the signature before processing an event. Because a callback can be retried, make
the receiver safe to process the same event more than once.

## Callback payloads

### Order callback

An Order callback is sent when the Order status changes to `completed` or
`canceled`.

| Parameter | Description |
| --- | --- |
| `id` | Unique ID of the Order. |
| `type` | Resource type. Always `orders`. |
| `status` | Order status. Possible values are `completed` and `canceled`. |

### Export callback

An Export callback is sent when processing is complete. For `cdr_in` and `cdr_out`
Exports created with API version `2026-04-16`, the callback includes the direct download
URL.

| Parameter | Description |
| --- | --- |
| `id` | Unique ID of the Export. |
| `type` | Resource type. Always `exports`. |
| `status` | Export status. Always `completed`. |
| `url` | Direct URL for downloading the completed export file. |

### Address Verification callback

An Address Verification callback is sent when the verification is approved or rejected.

| Parameter | Description |
| --- | --- |
| `id` | Unique ID of the Address Verification. |
| `type` | Resource type. Always `address_verifications`. |
| `status` | Verification status. Possible values are `approved` and `rejected`. |
| `reject_reason` | Rejection reason. This parameter is always included, but it is empty unless `status` is `rejected`. |
| `reject_comment` | Detailed compliance comment. This parameter is always included, but it is empty unless the verification is rejected and a comment is available. |

Note

The callback parameter is named `reject_reason` in the singular. The Address
Verification resource returned by the API uses the `reject_reasons` attribute in
the plural.

### Emergency Verification callback

An Emergency Verification callback is sent when the verification is approved or rejected.

| Parameter | Description |
| --- | --- |
| `id` | Unique ID of the Emergency Verification. |
| `type` | Resource type. Always `emergency_verifications`. |
| `status` | Verification status. Possible values are `approved` and `rejected`. |
| `reject_reasons` | Rejection reason or reasons. This parameter is always included, but it is empty unless `status` is `rejected`. |
| `reject_comment` | Detailed compliance comment. This parameter is always included, but it is empty unless the verification is rejected and a comment is available. |
| `emergency_calling_service_id` | ID of the related Emergency Calling Service. |

When the Emergency Calling Service is activated, DIDWW creates an Order record for the
resulting charge and sends an additional Order callback. That payload contains the Order
`id`, resource `type` `orders`, and `status` `completed` or
`canceled`.

### Outbound Trunk callback

An Outbound Trunk callback is sent when a trunk is blocked because its configured 24-hour
threshold was reached or when the trunk is unblocked. Each callback body is a JSON array,
even when it contains only one event.

| Parameter | Type | Description |
| --- | --- | --- |
| `id` | `string` | Unique ID of the Outbound Trunk. |
| `type` | `string` | Resource type. Always `voice_out_trunks`. |
| `status` | `string` | Trunk status. Possible values are `active` and `blocked`. |
| `threshold_reached` | `boolean` | Indicates whether the configured 24-hour threshold has been reached. |
| `created_at` | `string` | Date and time when the event was created. |

Example:

```
[
  {
    "id": "f36d1d17-bd16-42b9-af42-0cfe166bf3ec",
    "type": "voice_out_trunks",
    "status": "blocked",
    "threshold_reached": true,
    "created_at": "2017-06-25T08:21:41.795Z"
  }
]
```

## Validate callback requests

DIDWW signs callback requests so your application can verify their origin and integrity.
The signature is a hexadecimal HMAC-SHA1 digest sent in the
`X-DIDWW-Signature` header.

Use the API key that has `enable_callbacks` enabled as the signing secret. Keep this
key confidential. The API key is case-sensitive. Compare signatures using a constant-time
comparison function.

### SDK request validators

The DIDWW SDKs provide callback validation implementations:

- [Ruby request validator](https://github.com/didww/didww-v3-ruby/blob/master/lib/didww/callback/request_validator.rb)
- [PHP request validator](https://github.com/didww/didww-api-3-php-sdk/blob/master/src/Callback/RequestValidator.php)
- [Java request validator](https://github.com/didww/didww-api-3-java-sdk/blob/main/src/main/java/com/didww/sdk/callback/RequestValidator.java)
- [Python request validator](https://github.com/didww/didww-api-3-python-sdk/blob/main/src/didww/callback/request_validator.py)
- [TypeScript request validator](https://github.com/didww/didww-api-3-typescript-sdk/blob/main/src/callback/request-validator.ts)
- [Go validation example](https://github.com/didww/didww-api-3-go-sdk/blob/main/callback.go)

### Manual validation

To validate a callback without an SDK:

1. Normalize the full callback request URL. Include the scheme, host, explicit port, path,
   original query string, and fragment when present. Use port `443` for HTTPS or
   port `80` for HTTP when the URL does not specify a port.
2. Normalize the event payload:

   - For a form-encoded `post`, use all request-body fields.
   - For a `get`, use the event query parameters but exclude parameters already
     present in the configured `callback_url`.
   - For a JSON array callback, preserve the order of event objects. Normalize each object
     separately.
3. Sort the field names using case-sensitive Unix sorting order. For every field, append
   its name and value with no delimiter. For a JSON array, concatenate the normalized
   objects with no delimiter.
4. Append the normalized payload to the normalized URL.
5. Calculate an HMAC-SHA1 digest of the resulting string using the callback-enabled API key
   as the secret.
6. Hex-encode the digest and compare it with the `X-DIDWW-Signature` header.

### Order callback signature example

Assume DIDWW sends an Order `post` callback to:

```
https://mycompany.com/didww_callbacks?opaque=123
```

The form-encoded payload contains:

- `type=orders`
- `status=completed`
- `id=bf2cee72-6caa-4ae2-917e-bea01945691e`

Normalize the URL by adding the HTTPS port:

```
https://mycompany.com:443/didww_callbacks?opaque=123
```

Sort the payload fields by name as `id`, `status`, and `type`. Append each
name and value to the URL with no delimiters:

```
https://mycompany.com:443/didww_callbacks?opaque=123idbf2cee72-6caa-4ae2-917e-bea01945691estatuscompletedtypeorders
```

Using the example API key `szrdgh6547umt7tht7xbqhj6g9gdbyp7`, the expected
hexadecimal HMAC-SHA1 signature is:

```
30f66e9d72eb5e193051fd02952f70d8e934b4ff
```

Note

DIDWW uses HMAC-SHA1 rather than an unauthenticated SHA-1 digest. Keep the callback-enabled
API key secret because the security of the signature depends on that key.

## Allow callback source networks

If your callback endpoint uses an IP allowlist, permit these DIDWW source networks:

| Protocol | Network |
| --- | --- |
| IPv4 | `46.19.208.0/21` |
| IPv6 | `2a01:ad00::/32` |

## Handle delivery failures

DIDWW treats a non-`2xx` response or a response that takes longer than 60 seconds as
a failed delivery. DIDWW retries a failed callback up to nine times, for a maximum of ten
delivery attempts including the initial request.

Return a `2xx` response only after the callback has been accepted for processing.
The intervals below are measured before each retry.

| Attempt | Wait after the previous attempt |
| --- | --- |
| 2 | 1 minute |
| 3 | 10 minutes |
| 4 | 30 minutes |
| 5 | 1 hour |
| 6 | 3 hours |
| 7 | 6 hours |
| 8 | 12 hours |
| 9 | 1 day |
| 10 | 2 days |

## Test callbacks from a local environment

A callback endpoint running behind NAT is not reachable directly from DIDWW. For
development testing, a tunneling tool such as [ngrok](https://ngrok.com/docs/start) or
[localtunnel](https://theboroer.github.io/localtunnel-www/) can expose the local
endpoint through a public URL. Use the generated HTTPS URL as `callback_url` and
validate requests in the same way as in production.

On this page
