Managed Signing
Circle Payment Network (CPN)
4. Per-Payment Signature

Step 4: Per-Payment Signature (CPN V2)

POST
https://api-prod.useknit.io/api/v1/managed-signing/requests
Scopemanaged-signing:writeAuthBearer token

For each CPN V2 payment, Circle provides a messageToBeSigned (EIP-712 typed data). You sign it using this endpoint and return the signature to Circle.

How CPN V2 Works

1. Initiate payment with Circle → Circle returns `messageToBeSigned`
2. Sign with Knit API          → Knit returns `signature`
3. Submit to Circle            → Circle processes the payment

Body

{
  "walletId": "<local-wallet-id>",
  "network": "MATIC_MAINNET",
  "type": "eip712",
  "payload": {
    "typedData": {
      "primaryType": "PermitWitnessTransferFrom",
      "domain": {
        "name": "Permit2",
        "chainId": 137,
        "verifyingContract": "0x000000000022D473030F116dDEE9F6B43aC78BA3"
      },
      "types": {
        "PermitWitnessTransferFrom": [
          { "name": "permitted", "type": "TokenPermissions" },
          { "name": "spender", "type": "address" },
          { "name": "nonce", "type": "uint256" },
          { "name": "deadline", "type": "uint256" },
          { "name": "witness", "type": "..." }
        ],
        "TokenPermissions": [
          { "name": "token", "type": "address" },
          { "name": "amount", "type": "uint256" }
        ]
      },
      "message": {
        "permitted": {
          "token": "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
          "amount": "1000000"
        },
        "spender": "0x...",
        "nonce": "...",
        "deadline": "...",
        "witness": "..."
      }
    }
  }
}
FieldTypeDescription
walletIdstringYour local wallet ID
networkstringTarget network
typestringMust be eip712 for typed data signing
payload.typedDataobjectCircle's messageToBeSigned (EIP-712 format)

Important

The typedData object should be exactly as provided by Circle's API. Do not modify or camelCase the keys in types or message - they must match the EIP-712 schema exactly.

Request

curl "https://api-prod.useknit.io/api/v1/managed-signing/requests" \
  -H "Authorization: Bearer $KNIT_ACCESS_TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "walletId": "<local-wallet-id>",
    "network": "MATIC_MAINNET",
    "type": "eip712",
    "payload": {
        "typedData": { "...Circle messageToBeSigned..." }
    }
}'

Response

Response
{
  "statusCode": 201,
  "message": "Signing request created",
  "data": {
    "id": "<local-request-id>",
    "businessId": "<business-id>",
    "walletId": "<local-wallet-id>",
    "network": "MATIC_MAINNET",
    "type": "eip712",
    "status": "SIGNED",
    "payload": {
      "typedData": { "..." }
    },
    "signature": "0x...",
    "createdAt": "2026-01-20T18:30:40.912Z",
    "updatedAt": "2026-01-20T18:30:40.912Z"
  },
  "success": true
}

Using the Signature

Take the signature from the response and include it in Circle's payment submission step:

{
  "signature": "0x..."
}

Gasless Signing

Unlike the Permit2 approval step, EIP-712 signing is completely gasless. The signature is generated off-chain and no blockchain transaction is required.

Error Handling

ErrorCauseSolution
POLICY_DENIEDtypedData not allowed by policyEnsure typedData.allow: true in your policy
INVALID_TYPED_DATAMalformed EIP-712 dataVerify the typedData matches Circle's format exactly

Integration Example

// 1. Initiate payment with Circle
const circleResponse = await circle.initiatePayment({
  amount: "10.00",
  currency: "USD"
});
 
// 2. Sign with Knit
const knitResponse = await fetch('https://api-prod.useknit.io/api/v1/managed-signing/requests', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${accessToken}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    walletId: walletId,
    network: 'MATIC_MAINNET',
    type: 'eip712',
    payload: {
      typedData: circleResponse.messageToBeSigned
    }
  })
});
 
const { signature } = (await knitResponse.json()).data;
 
// 3. Submit to Circle
await circle.submitPayment({
  paymentId: circleResponse.paymentId,
  signature: signature
});