.NET client for DIDWW API v3.
The DIDWW API provides a simple yet powerful interface that allows you to fully integrate your own applications with DIDWW services. An extensive set of actions may be performed using this API, such as ordering and configuring phone numbers, setting capacity, creating SIP trunks and retrieving CDRs and other operational data.
The DIDWW API v3 is a fully compliant implementation of the JSON API specification.
This SDK uses JsonApiSerializer for JSON:API serialization and deserialization.
Read more https://doc.didww.com/api
This SDK sends the X-DIDWW-API-Version: 2026-04-16 header with every request by default.
| NuGet Version | Branch | DIDWW API Version |
|---|---|---|
| 3.x | main |
2026-04-16 |
| 2.x | 2022-05-10 |
2022-05-10 |
- .NET 9.0 or later
dotnet add package Didww.Api3using Didww.Api3;
var client = DidwwClient.NewBuilder()
.SetCredentials(new DidwwCredentials("your-api-key", DidwwEnvironment.Production))
.SetTimeout(TimeSpan.FromSeconds(30))
.Build();
// Check balance
var balance = (await client.Balance().FindAsync()).Data;
Console.WriteLine($"Balance: {balance.BalanceAmount}, Credit: {balance.Credit}");
// List DID groups with stock keeping units
var queryParams = new QueryParams().Include("stock_keeping_units");
var didGroups = await client.DidGroups().ListAsync(queryParams);For more examples visit examples/.
For details on obtaining your API key please visit https://doc.didww.com/api3/configuration.html
// Production
var client = DidwwClient.NewBuilder()
.SetCredentials(new DidwwCredentials("your-api-key", DidwwEnvironment.Production))
.SetTimeout(TimeSpan.FromSeconds(30))
.Build();
// Sandbox
var client = DidwwClient.NewBuilder()
.SetCredentials(new DidwwCredentials("your-api-key", DidwwEnvironment.Sandbox))
.Build();| Environment | Base URL |
|---|---|
Production |
https://api.didww.com/v3 |
Sandbox |
https://sandbox-api.didww.com/v3 |
You can pass a custom HttpMessageHandler for advanced configuration such as proxy support:
var handler = new HttpClientHandler
{
Proxy = new WebProxy("http://proxy.example.com:8080"),
UseProxy = true
};
var client = DidwwClient.NewBuilder()
.SetCredentials(new DidwwCredentials("your-api-key", DidwwEnvironment.Production))
.SetInnerHandler(handler)
.Build();using Didww.Api3.Http;
using Didww.Api3.Resource;
// Countries
var countries = await client.Countries().ListAsync();
var country = (await client.Countries().FindAsync("uuid")).Data;
// Regions
var regions = await client.Regions().ListAsync();
// Cities
var cities = await client.Cities().ListAsync();
// Areas
var areas = await client.Areas().ListAsync();
// NANPA Prefixes
var prefixes = await client.NanpaPrefixes().ListAsync();
// POPs (Points of Presence)
var pops = await client.Pops().ListAsync();
// DID Group Types
var types = await client.DidGroupTypes().ListAsync();
// DID Groups (with stock keeping units)
var groups = await client.DidGroups().ListAsync(new QueryParams().Include("stock_keeping_units"));
// Available DIDs
var available = await client.AvailableDids().ListAsync();
// Proof Types
var proofTypes = await client.ProofTypes().ListAsync();
// Public Keys
var publicKeys = await client.PublicKeys().ListAsync();
// Address Requirements
var requirements = await client.AddressRequirements().ListAsync();
// Balance (singleton)
var balance = (await client.Balance().FindAsync()).Data;// List DIDs
var dids = await client.Dids().ListAsync();
// Update DID - assign trunk and capacity
var did = (await client.Dids().FindAsync("uuid")).Data;
did.Description = "Updated";
did.CapacityLimit = 20;
did.VoiceInTrunk = VoiceInTrunk.Build("trunk-uuid");
did = (await client.Dids().UpdateAsync(did)).Data;using Didww.Api3.Resource.Configuration;
using Didww.Api3.Resource.Enums;
// Create SIP trunk
var sipConfig = new SipConfiguration
{
Username = "myuser",
Host = "203.0.113.1",
Port = 5060,
CodecIds = new List<Codec> { Codec.PCMU, Codec.PCMA, Codec.G729 },
TransportProtocolId = TransportProtocol.UDP,
SstRefreshMethodId = SstRefreshMethod.INVITE,
MediaEncryptionMode = MediaEncryptionMode.Disabled
};
var trunk = new VoiceInTrunk
{
Name = "My SIP Trunk",
Configuration = sipConfig
};
var trunkResponse = await client.VoiceInTrunks().CreateAsync(trunk);
// Create PSTN trunk
var pstnConfig = new PstnConfiguration { Dst = "558540420024" };
var pstnTrunk = new VoiceInTrunk
{
Name = "My PSTN Trunk",
Configuration = pstnConfig
};
await client.VoiceInTrunks().CreateAsync(pstnTrunk);
// Update trunk
var existingTrunk = VoiceInTrunk.Build("trunk-uuid");
existingTrunk.Name = "Renamed trunk";
await client.VoiceInTrunks().UpdateAsync(existingTrunk);
// Delete trunk
await client.VoiceInTrunks().DeleteAsync("trunk-uuid");var group = new VoiceInTrunkGroup
{
Name = "Primary Group",
CapacityLimit = 50
};
await client.VoiceInTrunkGroups().CreateAsync(group);Voice Out Trunks use a polymorphic AuthenticationMethod (2026-04-16). Three types are supported:
CredentialsAndIpAuthenticationMethod-- default method;UsernameandPasswordare server-generated and returned in the response.TwilioAuthenticationMethod-- requires aTwilioAccountSid.IpOnlyAuthenticationMethod-- read-only; can only be configured by DIDWW staff upon request. Cannot be set via the API.
using Didww.Api3.Resource.Configuration.AuthenticationMethod;
// NOTE: 203.0.113.0/24 is RFC 5737 TEST-NET-3 documentation space.
// Replace with the real CIDR of your SIP infrastructure.
var trunk = new VoiceOutTrunk
{
Name = "My Outbound Trunk",
OnCliMismatchAction = OnCliMismatchAction.ReplaceCli,
DefaultDid = Did.Build("did-uuid"),
AuthenticationMethod = new CredentialsAndIpAuthenticationMethod
{
AllowedSipIps = new List<string> { "203.0.113.0/24" },
}
};
var response = await client.VoiceOutTrunks().CreateAsync(trunk);
// response.Data.AuthenticationMethod as CredentialsAndIpAuthenticationMethod -- Username/Password server-generatedusing Didww.Api3.Resource.OrderItem;
// Order by SKU
var order = new Order
{
AllowBackOrdering = true,
Items = new List<OrderItemBase>
{
new DidOrderItem { SkuId = "sku-uuid", Qty = 2 }
}
};
var response = await client.Orders().CreateAsync(order);
// Order available DID
var order = new Order
{
Items = new List<OrderItemBase>
{
new AvailableDidOrderItem
{
SkuId = "sku-uuid",
AvailableDidId = "available-did-uuid"
}
}
};
// Order capacity
var order = new Order
{
Items = new List<OrderItemBase>
{
new CapacityOrderItem
{
CapacityPoolId = "pool-uuid",
Qty = 1
}
}
};var reservation = new DidReservation();
reservation.Description = "Reserved for client";
reservation.AvailableDid = AvailableDid.Build("available-did-uuid");
await client.DidReservations().CreateAsync(reservation);
// Delete reservation
await client.DidReservations().DeleteAsync("reservation-uuid");var group = new SharedCapacityGroup();
group.Name = "Shared Group";
group.SharedChannelsCount = 20;
group.CapacityPool = CapacityPool.Build("pool-uuid");
await client.SharedCapacityGroups().CreateAsync(group);var identity = new Identity
{
FirstName = "John",
LastName = "Doe",
PhoneNumber = "12125551234",
IdentityType = IdentityType.Personal,
Country = Country.Build("country-uuid")
};
await client.Identities().CreateAsync(identity);var address = new Address
{
CityName = "New York",
PostalCode = "10001",
AddressLine = "123 Main St",
Identity = Identity.Build("identity-uuid"),
Country = Country.Build("country-uuid")
};
await client.Addresses().CreateAsync(address);var export = new Export
{
ExportType = ExportType.CdrIn,
Filters = new Dictionary<string, object>
{
{ "from", "2026-04-01 00:00:00" },
{ "to", "2026-04-15 23:59:59" }
}
};
var response = await client.Exports().CreateAsync(export);
// Download when completed
var completed = (await client.Exports().FindAsync(response.Data.Id)).Data;
if (completed.Url != null)
{
await client.DownloadExportAsync(completed, "/tmp/export.csv");
}// List address verifications
var verifications = await client.AddressVerifications().ListAsync();
// Create address verification
var verification = new AddressVerification
{
CallbackUrl = "https://example.com/callback",
CallbackMethod = CallbackMethod.Post,
Address = Address.Build("address-uuid"),
Dids = new List<Did> { Did.Build("did-uuid") }
};
var result = await client.AddressVerifications().CreateAsync(verification);// List emergency requirements
var emergReqs = await client.EmergencyRequirements().ListAsync();
// Create emergency verification
var emergVerification = new EmergencyVerification
{
CallbackUrl = "https://example.com/callback",
CallbackMethod = CallbackMethod.Post,
Address = Address.Build("address-uuid"),
Dids = new List<Did> { Did.Build("did-uuid") }
};
var emergResult = await client.EmergencyVerifications().CreateAsync(emergVerification);
// List emergency calling services
var emergServices = await client.EmergencyCallingServices().ListAsync();// List DID history
var history = await client.DidHistory().ListAsync();
foreach (var entry in history.Data)
{
Console.WriteLine($"{entry.Action} {entry.CreatedAt}");
}using Didww.Api3.Http;
var queryParams = new QueryParams()
.Filter("country.id", "uuid")
.Filter("name", "Arizona")
.Include("country")
.Sort("name")
.Page(1, 25);
var regions = await client.Regions().ListAsync(queryParams);The SDK tracks which fields have been modified and sends only those fields in PATCH requests. This avoids overwriting server-side values that your code hasn't touched.
When you fetch a resource and modify it, only the changed fields are sent:
var did = (await client.Dids().FindAsync("uuid")).Data;
did.Description = "Updated description";
// PATCH payload includes only "description", not all attributes
did = (await client.Dids().UpdateAsync(did)).Data;Use Build(id) to create a lightweight resource for PATCH without fetching first:
var trunk = VoiceInTrunk.Build("trunk-uuid");
trunk.Name = "New name";
// PATCH payload includes only "name"
await client.VoiceInTrunks().UpdateAsync(trunk);Setting a property to null marks it as dirty and includes an explicit null in the payload, which clears the server-side value:
var did = Did.Build("uuid");
did.Description = null;
// PATCH payload includes "description": null
did = (await client.Dids().UpdateAsync(did)).Data;Setting a relationship to null sends "data": null for to-one relationships:
var did = Did.Build("uuid");
did.VoiceInTrunk = null;
// PATCH payload includes: "relationships": { "voice_in_trunk": { "data": null } }
did = (await client.Dids().UpdateAsync(did)).Data;Dirty tracking is automatically enabled on included (sideloaded) resources, so you can fetch with includes and update a related resource directly:
var qp = new QueryParams().Include("voice_in_trunk");
var did = (await client.Dids().FindAsync("uuid", qp)).Data;
var trunk = did.VoiceInTrunk;
trunk.Name = "Renamed trunk";
// PATCH payload includes only "name"
await client.VoiceInTrunks().UpdateAsync(trunk);using Didww.Api3.Exception;
try
{
await client.VoiceInTrunks().FindAsync("nonexistent");
}
catch (DidwwApiException e)
{
Console.WriteLine($"HTTP Status: {e.HttpStatus}");
foreach (var error in e.Errors)
{
Console.WriteLine($"Error: {error.Detail}");
}
}
catch (DidwwClientException e)
{
Console.WriteLine($"Client error: {e.Message}");
}The SDK provides an Encrypt utility for encrypting files before upload, using RSA-OAEP + AES-256-CBC (matching DIDWW's encryption requirements).
var encrypt = new Encrypt(client);
byte[] fileData = File.ReadAllBytes("document.pdf");
byte[] encryptedData = encrypt.EncryptData(fileData);
var fileIds = await client.UploadEncryptedFileAsync(
encryptedData,
"document.pdf",
encrypt.Fingerprint,
"My document"
);Validate incoming webhook callbacks from DIDWW using HMAC-SHA1 signature verification.
using Didww.Api3.Callback;
var validator = new RequestValidator("your-api-key");
var isValid = validator.Validate(
requestUrl, // full original URL
payloadParams, // Dictionary<string, string> of payload key-value pairs
signature // value of X-DIDWW-Signature header
);| Type | Class |
|---|---|
| SIP | SipConfiguration |
| PSTN | PstnConfiguration |
| Type | Class |
|---|---|
| DID | DidOrderItem |
| Capacity | CapacityOrderItem |
| Emergency | EmergencyOrderItem |
| Generic | GenericOrderItem |
| Resource | Class | Operations |
|---|---|---|
| Country | Country |
List, Find |
| Region | Region |
List, Find |
| City | City |
List, Find |
| Area | Area |
List, Find |
| NanpaPrefix | NanpaPrefix |
List, Find |
| Pop | Pop |
List, Find |
| DidGroupType | DidGroupType |
List, Find |
| DidGroup | DidGroup |
List, Find |
| AvailableDid | AvailableDid |
List, Find |
| ProofType | ProofType |
List, Find |
| PublicKey | PublicKey |
List, Find |
| AddressRequirement | AddressRequirement |
List, Find |
| SupportingDocumentTemplate | SupportingDocumentTemplate |
List, Find |
| Balance | Balance |
Find |
| Did | Did |
List, Find, Update, Delete |
| VoiceInTrunk | VoiceInTrunk |
List, Find, Create, Update, Delete |
| VoiceInTrunkGroup | VoiceInTrunkGroup |
List, Find, Create, Update, Delete |
| VoiceOutTrunk | VoiceOutTrunk |
List, Find, Create, Update, Delete |
| VoiceOutTrunkRegenerateCredential | VoiceOutTrunkRegenerateCredential |
Create |
| DidReservation | DidReservation |
List, Find, Create, Delete |
| CapacityPool | CapacityPool |
List, Find |
| SharedCapacityGroup | SharedCapacityGroup |
List, Find, Create, Update, Delete |
| Order | Order |
List, Find, Create |
| Export | Export |
List, Find, Create, Update |
| Address | Address |
List, Find, Create, Delete |
| AddressVerification | AddressVerification |
List, Create, Update |
| Identity | Identity |
List, Find, Create, Delete |
| EncryptedFile | EncryptedFile |
List, Find, Delete |
| PermanentSupportingDocument | PermanentSupportingDocument |
Create, Delete |
| Proof | Proof |
Create, Delete |
| AddressRequirementValidation | AddressRequirementValidation |
Create |
| DidHistory | DidHistory |
List |
| EmergencyRequirement | EmergencyRequirement |
List, Find |
| EmergencyRequirementValidation | EmergencyRequirementValidation |
Create |
| EmergencyCallingService | EmergencyCallingService |
List, Find, Delete |
| EmergencyVerification | EmergencyVerification |
List, Find, Create, Update |
The SDK distinguishes between date-only and datetime fields:
- Datetime fields are deserialized as
DateTimeOffset?:CreatedAt— present on most resourcesExpiresAt—Did,DidReservation,Proof,EncryptedFile(nullable)ActivatedAt—EmergencyCallingService(nullable)CanceledAt—EmergencyCallingService(nullable)
- Date-only fields (
Identity.BirthDate) are deserialized asDateOnly?. - Date-only fields kept as strings remain as
string?:CapacityPool.RenewDate,EmergencyCallingService.RenewDate—"YYYY-MM-DD"(nullable)DidOrderItem.BilledFrom,DidOrderItem.BilledTo
- String fields (not numeric):
EmergencyRequirement.EstimateSetupTime— e.g."7-14 days","1"EmergencyRequirement.RequirementRestrictionMessage— nullable
Important changes from previous API versions:
ExpireAtrenamed toExpiresAtonDidReservationandEncryptedFileRenewDateis a date-only string, NOT aDateTimeOffsetEstimateSetupTimeis a string, NOT an integer
var did = (await client.Dids().FindAsync("uuid")).Data;
Console.WriteLine(did.CreatedAt); // 2024-01-15T10:00:00+00:00
Console.WriteLine(did.ExpiresAt); // null or 2025-01-15T10:00:00+00:00
var identity = (await client.Identities().FindAsync("uuid")).Data;
Console.WriteLine(identity.BirthDate); // 1990-05-20The SDK provides enum types in Didww.Api3.Resource.Enums:
CallbackMethod, IdentityType, OrderStatus, ExportType, ExportStatus, CliFormat,
OnCliMismatchAction*, MediaEncryptionMode, DefaultDstAction, VoiceOutTrunkStatus,
EmergencyCallingServiceStatus, EmergencyVerificationStatus, DiversionRelayPolicy,
TransportProtocol, Codec, RxDtmfFormat, TxDtmfFormat, SstRefreshMethod,
ReroutingDisconnectCode, Feature, AreaLevel, AddressVerificationStatus, StirShakenMode,
DidHistoryAction, DidHistoryMethod
* ReplaceCli and RandomizeCli require additional account configuration. Contact DIDWW support to enable these values.
# Run tests
dotnet test
# Run tests with coverage
dotnet test --collect:"XPlat Code Coverage"
# Check formatting
dotnet format --verify-no-changes
# Build
dotnet buildTo publish a new version to NuGet:
- Update the version in
src/Didww.Api3/Didww.Api3.csproj - Commit and push to
main - Create and push a version tag:
git tag v1.0.0
git push origin v1.0.0The release workflow will automatically build, test, and publish the package to NuGet.
Bug reports and pull requests are welcome on GitHub at https://github.com/didww/didww-api-3-dotnet-sdk
The package is available as open source under the terms of the MIT License.